Index: .gitignore =================================================================== diff -u --- .gitignore (revision 0) +++ .gitignore (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,20 @@ +.gradle +build/ + +### IDEA Files +.idea +*.iml +*.ipr +*.iws +out + +### Eclipse files +.project +.settings +.classpath +bin + +mediaPath +media +temporaryMediaPath +dsLocaleImport Index: build.gradle =================================================================== diff -u --- build.gradle (revision 0) +++ build.gradle (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,180 @@ +buildscript { + repositories { + mavenLocal() + maven { url 'http://workhorse.lemanscorp.com/nexus/content/groups/public/' } + } + dependencies { + classpath "org.grails:grails-gradle-plugin:$grailsVersion" + classpath "org.grails.plugins:hibernate5:6.0.5" + classpath "org.grails.plugins:views-gradle:1.1.2" + + // Lemans + classpath 'net.saliman:gradle-cobertura-plugin:2.3.2' + } +} + +//version "0.1" - leave this out to get no version no. in the war filename - kkrebs + +group 'com.lemans.services' + +defaultTasks 'clean', 'check', 'cobertura' + +apply plugin:"eclipse" +apply plugin:"idea" +apply plugin:"war" +apply plugin:"org.grails.grails-web" +apply plugin:"org.grails.plugins.views-json" + +apply plugin: "jacoco" + + +// Lemans +apply plugin: 'net.saliman.cobertura' +apply plugin: 'codenarc' + + +repositories { + mavenLocal() + maven { url 'http://workhorse.lemanscorp.com/nexus/content/groups/public/' } +} + +dependencyManagement { + imports { + mavenBom "org.grails:grails-bom:$grailsVersion" + } + applyMavenExclusions false +} + +//Lemans +configurations { + compile.exclude module: 'commons-logging' +} + +dependencies { + testCompile "org.springframework.boot:spring-boot-starter-logging" //Lemans + compile "org.springframework.boot:spring-boot-autoconfigure" + compile "org.grails:grails-core" + + compile "org.springframework.boot:spring-boot-starter-actuator" + compile "org.springframework.boot:spring-boot-actuator-docs" //Lemans + + provided "org.springframework.boot:spring-boot-starter-tomcat" //Lemans + + compile "org.grails:grails-plugin-url-mappings" + compile "org.grails:grails-plugin-rest" + compile "org.grails:grails-plugin-codecs" + compile "org.grails:grails-plugin-interceptors" + compile "org.grails:grails-plugin-services" + compile "org.grails:grails-plugin-datasource" + compile "org.grails:grails-plugin-databinding" + compile "org.grails:grails-plugin-async" + compile "org.grails:grails-web-boot" + compile "org.grails:grails-logging" + compile "org.grails.plugins:cache" + compile "org.grails.plugins:hibernate5" + compile "org.hibernate:hibernate-core:5.1.2.Final" + compile "org.hibernate:hibernate-ehcache:5.1.2.Final" + compile "org.grails.plugins:views-json" + compile "org.grails.plugins:views-json-templates" + console "org.grails:grails-console" + profile "org.grails.profiles:rest-api" + + compile 'org.apache.poi:poi:3.14' + compile 'org.apache.poi:poi-ooxml:3.14' + compile 'org.apache.poi:poi-ooxml-schemas:3.13' + compile 'org.gsheets.kktec:gsheets:0.4.2' + compile 'org.grails.plugins:quartz:2.0.12' + + + provided "org.slf4j:slf4j-api:1.7.20" + provided "org.slf4j:jcl-over-slf4j:1.7.21" + provided "org.slf4j:jul-to-slf4j:1.7.21" + provided "org.slf4j:log4j-over-slf4j:1.7.21" + provided "ch.qos.logback:logback-core" + provided "ch.qos.logback:logback-classic" + + testCompile "org.grails:grails-plugin-testing" +// testCompile "org.grails:grails-datastore-rest-client" + + // Lemans + + + compile 'org.apache.poi:poi-ooxml-schemas:3.13' + compile 'org.apache.poi:ooxml-schemas:1.3' + compile 'org.apache.poi:poi:3.14' + + compile ('org.apache.poi:poi-ooxml:3.14') { + transitive = false + } + compile 'org.gsheets.kktec:gsheets:0.4.2' + + + compile 'lemans:xml-json:0.14' + compile 'org.apache.httpcomponents:httpclient:4.2.1' + compile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.2' + + compile 'org.apache.tika:tika-core:1.4' + + provided 'net.sourceforge.jtds:jtds:1.3.1' + + compile "org.grails.plugins:rabbitmq-native:3.4.4" + + compile 'com.lemans.grails.plugins:lemans-core:0.1.4c' + compile 'com.lemans.grails.plugins:lemans-security:0.1.2c' + compile 'com.lemans.grails.plugins:lemans-rest:0.1.0d' + + testCompile 'com.lemans.grails.plugins:lemans-testing:0.1.0' +} + +task wrapper(type: Wrapper) { + gradleVersion = gradleWrapperVersion +} + +//Extra Lemans Config Section: + +grails { + // deals with 'command line too long' issue when running Grails commands in Idea + pathingJar = true +} + +tasks.withType(Test) { + testLogging { + showStandardStreams = true + } + systemProperties System.properties +} + +tasks.withType(JavaExec) { + systemProperties System.properties +} + +codenarc { + toolVersion = '1.0' + configFile = file("${project.projectDir}/codenarc/rules.groovy") +} + +cobertura { + coverageExcludes = ['.*Application.*'] + +// NOTE: It looks like coverage has false negatives. This may fix it - kkrebs + coverageMergeDatafiles = [file("${project.buildDir.path}/cobertura/cobertura.ser"), + file("${project.buildDir.path}/cobertura/coberturaInput.ser")] +} + +// Investigating using JaCoCo instead of Cobertura - kkrebs +// This is experimental. Results leave a lot to be desired at this time, 2016-09-29 +// Execute task jacocoTestReport to try it. +test { + jacoco { + includes ['com/lemans/**'] + } +} +jacocoTestReport.dependsOn test, integrationTest +jacocoTestReport.doFirst{ + classDirectories = files('build/classes/main/com/lemans') +} +jacocoTestReport { +// executionData files('build/jacoco/unitTest.exec', 'build/jacoco/integrationTest.exec') + executionData integrationTest, test +// sourceSets sourceSets.main +} Index: codenarc/rules.groovy =================================================================== diff -u --- codenarc/rules.groovy (revision 0) +++ codenarc/rules.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,75 @@ +ruleset { + ruleset('rulesets/basic.xml') + ruleset('rulesets/braces.xml') + ruleset('rulesets/concurrency.xml') + ruleset('rulesets/convention.xml') { + exclude 'NoDef' + exclude 'NoTabCharacter' + exclude 'TrailingComma' + } + ruleset('rulesets/design.xml') { + exclude 'AbstractClassWithoutAbstractMethod' + exclude 'EmptyMethodInAbstractClass' + } + ruleset('rulesets/dry.xml') { + exclude 'DuplicateListLiteral' + exclude 'DuplicateNumberLiteral' + exclude 'DuplicateMapLiteral' + exclude 'DuplicateStringLiteral' + } + ruleset('rulesets/enhanced.xml') + ruleset('rulesets/exceptions.xml') + ruleset('rulesets/formatting.xml') { + exclude 'ClassJavadoc' + exclude 'ConsecutiveBlankLines' + LineLength { + length = 200 + } + SpaceAroundMapEntryColon { + characterAfterColonRegex = /\s/ + } + exclude 'TrailingWhitespace' + } + ruleset('rulesets/generic.xml') + ruleset('rulesets/grails.xml') { + exclude 'GrailsDomainHasEquals' + exclude 'GrailsDomainHasToString' + } + ruleset('rulesets/groovyism.xml') + ruleset('rulesets/imports.xml') + ruleset('rulesets/jdbc.xml') + ruleset('rulesets/junit.xml') + ruleset('rulesets/logging.xml') + ruleset('rulesets/naming.xml') { + exclude 'FactoryMethodName' + MethodName { // helps Spock + regex = /[a-zA-Z#][_#\w\s'"\(\)]*/ + } + PropertyName { + regex = /[a-z][_a-zA-Z0-9]*/ + } + } + ruleset('rulesets/security.xml') { + exclude 'JavaIoPackageAccess' + } + ruleset('rulesets/serialization.xml') + ruleset('rulesets/size.xml') { + CyclomaticComplexity { // Requires the GMetrics jar + maxMethodComplexity = 10 + } + MethodSize { + maxLines = 26 + doNotApplyToClassNames = '*Spec' + } + MethodSize { + maxLines = 41 + applyToClassNames = '*Spec' + } + } + ruleset('rulesets/unnecessary.xml') { + UnnecessaryBooleanExpression { + doNotApplyToClassNames = '*Spec*' + } + } + ruleset('rulesets/unused.xml') +} \ No newline at end of file Index: files/3c83a0cf-cac5-4888-8de9-8e9d56b48a11.xlsx =================================================================== diff -u Binary files differ Index: files/ProductExport.xls =================================================================== diff -u Binary files differ Index: files/ProductExportFinal.xlsx =================================================================== diff -u Binary files differ Index: files/bad_file.xlsx =================================================================== diff -u Binary files differ Index: files/fire.jpg =================================================================== diff -u Binary files differ Index: files/sample2.PNG =================================================================== diff -u Binary files differ Index: files/testForNoProductId.xlsx =================================================================== diff -u Binary files differ Index: gradle.properties =================================================================== diff -u --- gradle.properties (revision 0) +++ gradle.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,2 @@ +grailsVersion=3.2.4 +gradleWrapperVersion=3.1 Index: gradle/wrapper/gradle-wrapper.jar =================================================================== diff -u Binary files differ Index: gradle/wrapper/gradle-wrapper.properties =================================================================== diff -u --- gradle/wrapper/gradle-wrapper.properties (revision 0) +++ gradle/wrapper/gradle-wrapper.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,6 @@ +#Tue Nov 01 09:55:11 CDT 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip Index: gradlew =================================================================== diff -u --- gradlew (revision 0) +++ gradlew (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,169 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$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="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" Index: gradlew.bat =================================================================== diff -u --- gradlew.bat (revision 0) +++ gradlew.bat (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,84 @@ +@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 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= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +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 init + +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 + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +: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 %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega Index: grails-app/conf/application.groovy =================================================================== diff -u --- grails-app/conf/application.groovy (revision 0) +++ grails-app/conf/application.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,94 @@ +import javax.naming.Context +import javax.naming.InitialContext + +grails.databinding.dateFormats = ["yyyy-MM-dd'T'hh:mm:ss", 'yyyy-MM-dd'] + +environments { + production { + rabbitmq { + connections = [ + [ + name : "main", + virtualHost: ((Context) (new InitialContext().lookup("java:comp/env"))).lookup("rabbitMQVirtualHost").toString(), + host : ((Context) (new InitialContext().lookup("java:comp/env"))).lookup("rabbitMQHost").toString(), + username : ((Context) (new InitialContext().lookup("java:comp/env"))).lookup("rabbitMQUserName").toString(), + password : ((Context) (new InitialContext().lookup("java:comp/env"))).lookup("rabbitMQPassword").toString() + ] + ] + queues = [ + [ + name : ((Context) (new InitialContext().lookup("java:comp/env"))).lookup("usToEuSyncQueue").toString(), + durable : true, + exchange: ((Context) (new InitialContext().lookup("java:comp/env"))).lookup("usToEuSyncExchange").toString(), + binding : ((Context) (new InitialContext().lookup("java:comp/env"))).lookup("usToEuSyncBinding").toString(), + arguments: ['x-message-ttl': 18000000, 'x-dead-letter-exchange': 'lemans-us-eu-dead-letter-exchange', 'x-dead-letter-routing-key': 'lemans-us-eu-dead-letter-routing-key'] + ] + ] + exchanges = [ + [ + name : ((Context) (new InitialContext().lookup("java:comp/env"))).lookup("usToEuSyncExchange").toString(), + type : "direct", + durable: true + ] + ] + } + } + development { + rabbitmq { + connections = [ + [ + name : "main", + host : "dev-rabbitmq01vm.lemanscorp.com", + username: "lemans", + password: "o7GgEz2pA9mA", + virtualHost: "/" + ] + ] + queues = [ + [ + name : "lemans-us-eu-queue", + durable : true, + exchange: "lemans-us-eu-exchange", + binding : "lemans-us-eu-routing-key", + arguments: ['x-message-ttl': 18000000, 'x-dead-letter-exchange': 'lemans-us-eu-dead-letter-exchange', 'x-dead-letter-routing-key': 'lemans-us-eu-dead-letter-routing-key'] + ] + ] + exchanges = [ + [ + name : "lemans-us-eu-exchange", + type : "direct", + durable: true + ] + ] + } + } + test { + rabbitmq { + connections = [ + [ + name : "main", + host : "dev-rabbitmq01vm.lemanscorp.com", + username: "lemans", + password: "o7GgEz2pA9mA", + virtualHost: "/" + ] + ] + queues = [ + [ + name : "lemans-us-eu-queue", + durable : true, + exchange: "lemans-us-eu-exchange", + binding : "lemans-us-eu-routing-key", + arguments: ['x-message-ttl': 18000000, 'x-dead-letter-exchange': 'lemans-us-eu-dead-letter-exchange', 'x-dead-letter-routing-key': 'lemans-us-eu-dead-letter-routing-key'] + ] + ] + exchanges = [ + [ + name : "lemans-us-eu-exchange", + type : "direct", + durable: true + ] + ] + } + } +} Index: grails-app/conf/application.properties =================================================================== diff -u --- grails-app/conf/application.properties (revision 0) +++ grails-app/conf/application.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,2 @@ +# Spring Boot config for jmx to avoid naming conflicts when multiple apps in same Tomcat +spring.jmx.default-domain=ds-service Index: grails-app/conf/application.yml =================================================================== diff -u --- grails-app/conf/application.yml (revision 0) +++ grails-app/conf/application.yml (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,109 @@ +--- +grails: + profile: rest-api + codegen: + defaultPackage: com.lemans.ds + spring: + transactionManagement: + proxies: false +info: + app: + name: '@info.app.name@' + version: '@info.app.version@' + grailsVersion: '@info.app.grailsVersion@' +spring: + groovy: + template: + check-template-location: false +# Spring Actuator Endpoints are Disabled by Default +endpoints: + enabled: false + jmx: + enabled: true + +--- +grails: + mime: + disable: + accept: + header: + userAgents: + - Gecko + - WebKit + - Presto + - Trident + types: + json: + - application/json + - text/json + hal: + - application/hal+json + - application/hal+xml + xml: + - text/xml + - application/xml + atom: application/atom+xml + css: text/css + csv: text/csv + js: text/javascript + rss: application/rss+xml + text: text/plain + all: '*/*' + urlmapping: + cache: + maxsize: 1000 + controllers: + defaultScope: singleton + converters: + encoding: UTF-8 + +--- + +grails: + controllers: + upload: + maxFileSize: 2000000 + maxRequestSize: 2000000 + +--- +hibernate: + naming_strategy: org.hibernate.cfg.DefaultNamingStrategy + cache: + queries: false + use_second_level_cache: false + use_query_cache: false + region.factory_class: org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory + format_sql: true + dialect: org.hibernate.dialect.SQLServer2008Dialect + +dataSource: + dbCreate: none + pooled: true + jmxExport: true + driverClassName: net.sourceforge.jtds.jdbc.Driver + +environments: + development: + dataSource: + url: jdbc:jtds:sqlserver://dev-dbprod02vm/PartsSource_DS + username: partssource_ds_user + password: DevPassword1 + logSql: true + test: + dataSource: + url: jdbc:jtds:sqlserver://dev-dbprod02vm/PartsSource_DS + username: partssource_ds_user + password: DevPassword1 + logSql: true + production: + dataSource: + jndiName: java:comp/env/jdbc/PartsSource_DS + logSql: false +--- +--- +server: + contextPath: /ds-service + +management: + context_path: /admin + Index: grails-app/conf/logback.groovy =================================================================== diff -u --- grails-app/conf/logback.groovy (revision 0) +++ grails-app/conf/logback.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,34 @@ +import grails.util.BuildSettings +import grails.util.Environment + +// See http://logback.qos.ch/manual/groovy.html for details on configuration +appender('STDOUT', ConsoleAppender) { + encoder(PatternLayoutEncoder) { + pattern = "%level %logger - %msg%n" + } +} + +if (Environment.TEST ||Environment.DEVELOPMENT) { + logger 'org.hibernate.type.descriptor.sql.BasicBinder', TRACE, ['STDOUT'] + logger 'groovy.sql', ALL, ['STDOUT'] +} + +def targetDir = BuildSettings.TARGET_DIR +if (Environment.isDevelopmentMode() && targetDir != null) { + appender("FULL_STACKTRACE", FileAppender) { + file = "${targetDir}/stacktrace.log" + append = true + encoder(PatternLayoutEncoder) { + pattern = "%level %logger - %msg%n" + } + } + logger("StackTrace", ERROR, ['FULL_STACKTRACE'], false) + root(ERROR, ['STDOUT', 'FULL_STACKTRACE']) +} +else { + root(ERROR, ['STDOUT']) +} + + + + Index: grails-app/conf/spring/resources.groovy =================================================================== diff -u --- grails-app/conf/spring/resources.groovy (revision 0) +++ grails-app/conf/spring/resources.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,65 @@ +import com.lemans.reporting.SsrReporter +import grails.util.Environment + +// Place your Spring DSL code here +beans = { + + Environment current = Environment.current + if (current != Environment.PRODUCTION) { + authServiceContext(String, 'http://services2.dev.lemanscorp.com/auth-service/verifyRequest') + temporaryMediaPath(String, 'temporaryMediaPath') + sqlServerReportHost(String, 'http://dev-dbprod03vm') + reportServerExportFolder(String, '/DigitalServicesExport') + mediaPath(String, 'mediaPath/') + mediaPrefix(String, 'media') + dsLocaleImport(String, 'dsLocaleImport/') + sqlServerReportApiAuth(String, 'bGVtYW5zY29ycFxzc3JzaTo0MiFHSDJIWC1mN1Q=') + usToEuSyncQueue(String, "lemans-us-eu-queue") + usToEuSyncExchange(String, "lemans-us-eu-exchange") + usToEuSyncBinding(String, "lemans-us-eu-routing-key") + usToEuSyncToggle(Boolean, false) + } else { + authServiceContext(org.springframework.jndi.JndiObjectFactoryBean) { + jndiName = 'java:comp/env/authServiceContext' + } + temporaryMediaPath(org.springframework.jndi.JndiObjectFactoryBean) { + jndiName = 'java:comp/env/temporaryMediaPath' + } + sqlServerReportHost(org.springframework.jndi.JndiObjectFactoryBean) { + jndiName = 'java:comp/env/sqlServerReportHost' + } + reportServerExportFolder(org.springframework.jndi.JndiObjectFactoryBean) { + jndiName = 'java:comp/env/reportServerExportFolder' + } + mediaPath(org.springframework.jndi.JndiObjectFactoryBean) { + jndiName = 'java:comp/env/mediaPath' + } + mediaPrefix(org.springframework.jndi.JndiObjectFactoryBean) { + jndiName = 'java:comp/env/mediaPrefix' + } + dsLocaleImport(org.springframework.jndi.JndiObjectFactoryBean) { + jndiName = 'java:comp/env/dsLocaleImport' + } + sqlServerReportApiAuth(org.springframework.jndi.JndiObjectFactoryBean) { + jndiName = 'java:comp/env/sqlServerReportApiAuth' + } + usToEuSyncQueue(org.springframework.jndi.JndiObjectFactoryBean) { + jndiName = 'java:comp/env/usToEuSyncQueue' + } + usToEuSyncExchange(org.springframework.jndi.JndiObjectFactoryBean) { + jndiName = 'java:comp/env/usToEuSyncExchange' + } + usToEuSyncBinding(org.springframework.jndi.JndiObjectFactoryBean) { + jndiName = 'java:comp/env/usToEuSyncBinding' + } + usToEuSyncToggle(org.springframework.jndi.JndiObjectFactoryBean) { + jndiName = 'java:comp/env/usToEuSyncToggle' + } + } + + ssrReporter(SsrReporter) { + httpClient = ref('httpClient') + sqlServerReportApiAuth = ref('sqlServerReportApiAuth') + sqlServerReportHost = ref('sqlServerReportHost') + } +} Index: grails-app/controllers/com/lemans/ApplicationController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ApplicationController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ApplicationController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,15 @@ +package com.lemans + +import grails.core.GrailsApplication +import grails.plugins.GrailsPluginManager +import grails.plugins.PluginManagerAware + +class ApplicationController implements PluginManagerAware { + + GrailsApplication grailsApplication + GrailsPluginManager pluginManager + + def index() { + [grailsApplication: grailsApplication, pluginManager: pluginManager] + } +} Index: grails-app/controllers/com/lemans/UrlMappings.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/UrlMappings.groovy (revision 0) +++ grails-app/controllers/com/lemans/UrlMappings.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,392 @@ +package com.lemans + +@SuppressWarnings('LineLength') +@SuppressWarnings('AbcMetric') +class UrlMappings { + + static mappings = { + + "/dm/$dm/part/$partNumber/fitment"(controller: 'partFitment') { + action = [POST: 'add', GET: 'fitmentsByPart'] + } + + "/dm/$dm/part/$partNumber/fitment/$fitmentId"(controller: 'partFitment') { + action = [DELETE: 'delete'] + } + + "/dm/$dm/make"(controller: 'make') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/localeImportJob"(controller: 'bulkLocaleImport') { + action = [POST: 'add', GET: 'index'] + } + + "/dm/$dm/localeImportJob/$importFileId"(controller: 'bulkLocaleImport') { + action = [GET: 'show', PUT: 'update', DELETE: 'remove'] + } + + "/dm/$dm/localeImportJob/$importFileId/file/$fileId/download"(controller: 'bulkLocaleImport') { + action = [GET: 'download'] + } + + "/dm/$dm/report/$reportName/download"(controller: 'bulkLocaleExport') { + action = [GET: 'downloadBulkExportFile'] + } + + "/dm/$dm/make/$makeId"(controller: 'make') { + action = [GET: 'show', PUT: 'update', DELETE: 'remove'] + } + + "/dm/$dm/make/$makeId/model"(controller: 'model') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/make/$makeId/model/$modelId"(controller: 'model') { + action = [GET: 'show', PUT: 'update', DELETE: 'remove'] + } + + "/dm/$dm/make/$makeId/model/$modelId/segment/$segmentCode"(controller: 'segment') { + action = [PUT: 'addUpdateOrDeleteModelSegment'] + } + + "/dm/$dm/make/$makeId/model/$modelId/year"(controller: 'modelYear') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/make/$makeId/model/$modelId/year/$yearId"(controller: 'modelYear') { + action = [DELETE: 'remove'] + } + + "/dm/$dm/make/$makeId/segment"(controller: 'segment') { + action = [GET: 'segments'] + } + + "/dm/$dm/make/$makeId/segment/$segmentCode/value"(controller: 'segment') { + action = [GET: 'segmentValues'] + } + + "/dm/$dm/search/$searchType/filter/$filterType"(controller: 'genericSearch') { + action = [GET: 'filters'] + } + + "/dm/$dm/search/$searchType/filter/$filterName/options"(controller: 'genericSearch') { + action = [GET: 'filterOptions'] + } + + "/dm/$dm/search/$searchType"(controller: 'genericSearch') { + action = [GET: 'search'] + } + + "/dm/$dm/mediaClassification"(controller: 'media') { + action = [GET: 'classifications'] + } + + "/dm/$dm/media"(controller: 'media') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/media/$mediaId"(controller: 'media') { + action = [GET: 'show', PUT: 'update', DELETE: 'remove'] + } + + "/dm/$dm/media/$mediaId/download"(controller: 'media') { + action = [GET: 'download'] + } + + "/dm/$dm/media/entity"(controller: 'mediaEntity') { + action = [POST: 'bulkAddOrRemoveMediaEntity'] + } + + "/dm/$dm/media/$mediaId/entity"(controller: 'mediaEntity') { + action = [GET: 'index'] + } + + "/dm/$dm/media/$mediaId/entity/$entityId"(controller: 'mediaEntity') { + action = [PUT: 'updateMediaEntity'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId/feature/$productFeatureId/move"(controller: 'productFeature') { + action = [PUT: 'move'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId/feature"(controller: 'productFeature') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId/feature/$productFeatureId"(controller: 'productFeature') { + action = [GET: 'show', PUT: 'update', DELETE: 'remove'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId/splitValidation"(controller: 'product') { + action = [POST: 'splitValidation'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product"(controller: 'product') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId"(controller: 'product') { + action = [GET: 'show', PUT: 'update', DELETE: 'remove'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$categoryId/product"(controller: 'categoryProduct') { + action = [POST: 'addOrRemoveAssociation'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId/media"(controller: 'mediaEntity') { + action = [GET: 'getMediaForProduct', POST: 'addMediaToProduct'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId/media/$mediaId"(controller: 'mediaEntity') { + action = [DELETE: 'removeMediaOnProduct'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId/part"(controller: 'productPart') { + action = [GET: 'index', POST: 'bulkAddOrRemoveOperation'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId/attribute"(controller: 'productMerchandise') { + action = [GET: 'getMerchandiseProductAttributeDetails'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId/attribute/$categoryAttributeId"(controller: 'productMerchandise') { + action = [PUT: 'update'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId/part/$partNumber"(controller: 'productPart') { + action = [GET: 'show', DELETE: 'remove'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$categoryId/part"(controller: 'categoryPart') { + action = [PUT: 'addOrRemove'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$categoryId/subComCode"(controller: 'categorySubComCode') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$categoryId/subComCode/$subComCodeId"(controller: 'categorySubComCode') { + action = [GET: 'show', DELETE: 'remove'] + } + + "/dm/$dm/subComCode"(controller: 'subComCode') { + action = [GET: 'index'] + } + + "/dm/$dm/subComCode/$subComCode"(controller: 'subComCode') { + action = [GET: 'show'] + } + + "/dm/$dm/part"(controller: 'part') { + action = [GET: 'index'] + } + + "/dm/$dm/part/$partNumber"(controller: 'part') { + action = [GET: 'show', PUT: 'update'] + } + + "/dm/$dm/part/$partNumber/media"(controller: 'mediaEntity') { + action = [GET: 'getMediaForPart', POST: 'addMediaToPart'] + } + + "/dm/$dm/part/$partNumber/media/$mediaId"(controller: 'mediaEntity') { + action = [DELETE: 'removeMediaOnPart'] + } + + "/dm/$dm/attributeName"(controller: 'attributeName') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/attributeName/$id"(controller: 'attributeName') { + action = [GET: 'show', PUT: 'update', DELETE: 'remove'] + } + + "/dm/$dm/attributeValue"(controller: 'attributeValue') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/attributeValue/$id"(controller: 'attributeValue') { + action = [GET: 'show', PUT: 'update', DELETE: 'remove'] + } + + "/dm/$dm/attributeName/$attributeNameId/attributeValue"(controller: 'attributeValue') { + action = [GET: 'attributeValuesByAttributeNameIdInCategoryContext'] + } + + "/dm/$dm/part/attribute"(controller: 'partAttribute') { + action = [POST: 'bulkAddOrRemoveOperation'] + } + + "/dm/$dm/attributeName/$attributeNameId/part"(controller: 'attributePart') { + action = [GET: 'partsByAttributeName'] + } + + "/dm/$dm/attributeValue/$attributeValueId/part"(controller: 'attributePart') { + action = [GET: 'partsByAttributeValue'] + } + + "/dm/$dm/attributeName/$attributeNameId/category"(controller: 'attributeCategory') { + action = [GET: 'categoriesByAttributeName'] + } + + "/dm/$dm/attributeValue/$attributeValueId/category"(controller: 'attributeCategory') { + action = [GET: 'categoriesByAttributeValue'] + } + + "/dm/$dm/part/$partNumber/attribute/$attributeNameId/value"(controller: 'partAttribute') { + action = [POST: 'add', PUT: 'bulkReplaceOperation'] + } + + "/dm/$dm/part/$partNumber/attribute/$attributeNameId/value/$attributeValueId" (controller: 'partAttribute') { + action = [DELETE: 'remove'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$id/move"(controller: 'category') { + action = [PUT: 'move'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$id"(controller: 'category') { + action = [GET: 'show', PUT: 'update', DELETE: 'remove'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category"(controller: 'category') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$categoryId/details"(controller: 'category') { + action = [GET: 'details'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$categoryId/part/details"(controller: 'partAttribute') { + action = [POST: 'categoryPartDetails'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$categoryId/attribute"(controller: 'categoryAttribute') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$categoryId/attribute/$attributeNameId"(controller: 'categoryAttribute') { + action = [GET: 'showByCompositeKey', PUT: 'update', DELETE: 'remove'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$categoryId/attribute/$attributeNameId/move"(controller: 'categoryAttribute') { + action = [PUT: 'move'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$categoryId/attribute/$attributeNameId/value"(controller: 'categoryAttributeValue') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$categoryId/attribute/$attributeNameId/value/$attributeValueId" (controller: 'categoryAttributeValue') { + action = [GET: 'showByCompositeKey', PUT: 'update', DELETE: 'remove'] + } + + "/dm/$dm/catalog/$catalogInstanceId/category/$categoryId/attribute/$attributeNameId/value/$attributeValueId/move" (controller: 'categoryAttributeValue') { + action = [PUT: 'move'] + } + + "/dm/$dm/brandSearch"(parseRequest: true, controller: 'brandSearch') { + action = [GET: 'brandSearch'] + } + + '/stuff'(controller: 'stuff') { + action = [GET: 'index'] + } + //partAssociation + + "/dm/$dm/part/$partNumber/relatedPart/$id"(controller: 'partAssociation') { + action = [PUT: 'update', DELETE: 'remove', GET: 'show'] + } + + "/dm/$dm/part/$partNumber/relatedPart"(controller: 'partAssociation') { + action = [GET: 'relatedParts', POST: 'add'] + } + + "/dm/$dm/part/$partNumber/referralPart"(controller: 'partAssociation') { + action = [GET: 'referralParts'] + } + + //productAssociation + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId/relatedProduct/$id"(controller: 'productAssociation') { + action = [PUT: 'update', DELETE: 'remove', GET: 'show'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId/relatedProduct"(controller: 'productAssociation') { + action = [GET: 'relatedProducts', POST: 'add'] + } + + "/dm/$dm/catalog/$catalogInstanceId/product/$productId/referralProduct"(controller: 'productAssociation') { + action = [GET: 'referralProducts'] + } + + "/dm/$dm/flag"(controller: 'flag') { + action = [GET: 'index'] + } + + "/dm/$dm/$entityClass/flag"(controller: 'flag') { + action = [GET: 'index'] + } + + "/dm/$dm/$entityClass/$entityId/flag/$flagId"(controller: 'flagValue') { + action = [GET: 'show'] + } + + "/dm/$dm/$entityClass/$entityId/flag"(controller: 'flagValue') { + action = [GET: 'index', POST: 'addOrDeleteFlagValues'] + } + + //publicationcategory + "/dm/$dm/publicationCategory"(controller: 'publicationCategory') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/publicationCategory/$categoryId"(controller: 'publicationCategory') { + action = [GET: 'show', PUT: 'update', DELETE: 'delete'] + } + + "/dm/$dm/publicationCategory/$categoryId/move"(controller: 'publicationCategory') { + action = [PUT: 'move'] + } + + //ProductPublicationCategory + "/dm/$dm/publicationCategory/$categoryId/product"(controller: 'productPublicationCategory') { + action = [GET: 'byCategoryId', POST: 'add'] + } + + "/dm/$dm/product/$productId/publicationCategory"(controller: 'productPublicationCategory') { + action = [GET: 'byProductId'] + } + + "/dm/$dm/publicationCategory/$categoryId/product/$productId"(controller: 'productPublicationCategory') { + action = [DELETE: 'delete'] + } + + //Explosion Diagram + "/dm/$dm/explosionDiagram"(controller: 'explosionDiagram') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/explosionDiagram/$explosionDiagramId"(controller: 'explosionDiagram') { + action = [GET: 'show', PUT: 'update', DELETE: 'delete'] + } + + //publicationCategoryAttribute + "/dm/$dm/publicationCategory/$categoryId/attribute"(controller: 'publicationCategoryAttribute') { + action = [GET: 'index', POST: 'add'] + } + + "/dm/$dm/publicationCategory/$categoryId/attributeOptions"(controller: 'publicationCategoryAttribute') { + action = [GET: 'attributeOptions'] + } + + "/dm/$dm/publicationCategory/$categoryId/attribute/$attributeNameId"(controller: 'publicationCategoryAttribute') { + action = [GET: 'showByCompositeKey', DELETE: 'delete'] + } + + "/dm/$dm/publicationCategory/$categoryId/attribute/$attributeNameId/move"(controller: 'publicationCategoryAttribute') { + action = [PUT: 'move'] + } + } +} Index: grails-app/controllers/com/lemans/ds/ReportAndLocaleCommonsController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/ReportAndLocaleCommonsController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/ReportAndLocaleCommonsController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,88 @@ +package com.lemans.ds + +import com.lemans.LemansApiController +import com.lemans.ds.rabbit.QueueRequestType +import com.lemans.ds.rabbit.RequestPayload + +/** + * Created by MUmachi on 10/9/2017. + */ +class ReportAndLocaleCommonsController extends LemansApiController { + + def rabbitMessageService + + def usToEuSyncToggle + + private static final List VALID_LOCALES = ['de', 'it', 'es', 'fr'] + + boolean validateLocale(String locale) { + VALID_LOCALES.contains(locale) + } + + boolean isDefaultLocale() { + locale == Locale.default.toString() + } + + def renderLocaleError(String locale) { + renderErrors(["locale with value [$locale] is not contained within the list [$VALID_LOCALES]"]) + } + + @SuppressWarnings(['CouldBeSwitchStatement'])//TODO: remove this, codenarc complaing for some reason + protected void renderMQObject(data, QueueRequestType requestType, id = null) { + renderObject(data, id) { + if (usToEuSyncToggle == true) { + rabbitMessageService.publishToUsTOEuQueue(new RequestPayload(requestType: requestType, id: data.id)) + } + } + } + + protected void renderMQDelete(data, QueueRequestType requestType, Iterable messages = null) { + renderDelete(data, messages) { + if (usToEuSyncToggle == true) { + rabbitMessageService.publishToUsTOEuQueue(new RequestPayload(requestType: requestType, id: data.id)) + } + } + } + + protected void renderMQObjectSpecial(data, QueueRequestType requestType, Object optionalPayload = null, id = null) { + renderObject(data, id) { + if (usToEuSyncToggle == true) { + rabbitMessageService.publishToUsTOEuQueue(new RequestPayload(requestType: requestType, id: data.id, body: optionalPayload)) + } + } + } + + void renderObject(data, id = null, Closure closure) { + if (data == null) { notFound() } + else if (data.hasErrors()) { renderErrors(data) } + else { + closure() + show(id ?: data.id) + } + } + + void renderDelete(data, Iterable messages = null, Closure closure) { + if (data == null) { notFound(messages) } + else if (data.hasErrors()) { renderErrors(data) } + else { + closure() + renderEmpty() + } + } + + @SuppressWarnings(['CatchException']) + protected generateReport(Map criteria, response, ssrReporter) { + try { + ssrReporter.renderSSRSReport("${criteria.url}${criteria.urlParams}", criteria.reportFormat, response) + } catch (Exception e) { + def rootCause = e.cause.toString() + if (rootCause.contains('ConnectTimeoutException')) { + renderErrors(['Connection Timed out, Try again']) + } else if (rootCause.contains('SocketTimeoutException')) { + renderErrors(['Socket Timed out, Try again']) + } else { + renderErrors(['Error with Reporting Server, Please Contact Helpdesk']) + } + } + } +} Index: grails-app/controllers/com/lemans/ds/SubComCodeController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/SubComCodeController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/SubComCodeController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,18 @@ +package com.lemans.ds + +import com.lemans.LemansApiController + +class SubComCodeController extends LemansApiController { + + def commonService + + def index() { + List data = commonService.findSubComCodes(common()) + renderList data + } + + def show(String subComCode) { + Map data = commonService.findSubComCode(common() + [subComCode: subComCode]) + renderOne data + } +} Index: grails-app/controllers/com/lemans/ds/attribute/AttributeCategoryController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/attribute/AttributeCategoryController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/attribute/AttributeCategoryController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,25 @@ +package com.lemans.ds.attribute + +import com.lemans.LemansApiController + +/** + * Created by vramisetti on 8/18/2017. + */ +class AttributeCategoryController extends LemansApiController { + + def attributeService + + def categoriesByAttributeName(Integer attributeNameId) { + if (attributeNameId) { renderPaginated(attributeService.categoriesByAttributeName(criteria())) } + else { renderErrors(['attributeNameId is required']) } + } + + def categoriesByAttributeValue(Integer attributeValueId) { + if (attributeValueId) { renderPaginated(attributeService.categoriesByAttributeValue(criteria())) } + else { renderErrors(['attributeValueId is required']) } + } + + private Map criteria() { + filters(['attributeNameId', 'attributeValueId']) + pagination() + common() + } +} Index: grails-app/controllers/com/lemans/ds/attribute/AttributeNameController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/attribute/AttributeNameController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/attribute/AttributeNameController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,47 @@ +package com.lemans.ds.attribute + +import static com.lemans.ds.rabbit.QueueRequestType.ATTRIBUTE_NAME_CREATE +import static com.lemans.ds.rabbit.QueueRequestType.ATTRIBUTE_NAME_DELETE +import static com.lemans.ds.rabbit.QueueRequestType.ATTRIBUTE_NAME_UPDATE + +import com.lemans.ds.ReportAndLocaleCommonsController + +class AttributeNameController extends ReportAndLocaleCommonsController { + + def attributeNameService + def attributeNameManagerService + + def index() { + Map criteria = common() + pagination() + renderPaginated attributeNameService.findAttributeNames(criteria) + } + + def show(Integer id) { + renderOne attributeNameService.findAttributeNameById([attributeNameId: id, locale: locale]) + } + + def add() { + Map criteria = request.JSON + common() + AttributeName attributeName = attributeNameManagerService.createAttributeName(criteria, auditUserName) + renderMQObject(attributeName, ATTRIBUTE_NAME_CREATE) + } + + def update(Integer id) { + Map criteria = request.JSON + [attributeNameId: id, locale: locale] + if (isDefaultLocale()) { + AttributeName attributeName = attributeNameManagerService.updateAttributeName(criteria, id, auditUserName) + renderMQObject(attributeName, ATTRIBUTE_NAME_UPDATE) + } else { + AttributeNameLocale attributeNameLocale = attributeNameManagerService.updateAttributeNameWithLocale(criteria, auditUserName) + renderObject(attributeNameLocale, attributeNameLocale?.attributeNameId) + } + } + + def remove(Integer id) { + AttributeName attributeName + if (id != null) { + attributeName = attributeNameManagerService.deleteAttributeName(id, auditUserName) + } + renderMQDelete(attributeName, ATTRIBUTE_NAME_DELETE) + } +} Index: grails-app/controllers/com/lemans/ds/attribute/AttributePartController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/attribute/AttributePartController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/attribute/AttributePartController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,25 @@ +package com.lemans.ds.attribute + +import com.lemans.LemansApiController + +/** + * Created by vramisetti on 8/18/2017. + */ +class AttributePartController extends LemansApiController { + + def attributeService + + def partsByAttributeName(Integer attributeNameId) { + if (attributeNameId) { renderPaginated(attributeService.partsByAttributeName(criteria())) } + else { renderErrors(['attributeNameId is required']) } + } + + def partsByAttributeValue(Integer attributeValueId) { + if (attributeValueId) { renderPaginated(attributeService.partsByAttributeValue(criteria())) } + else { renderErrors(['attributeValueId is required']) } + } + + private Map criteria() { + filters(['attributeNameId', 'attributeValueId', 'categoryId']) + pagination() + } +} Index: grails-app/controllers/com/lemans/ds/attribute/AttributeValueController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/attribute/AttributeValueController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/attribute/AttributeValueController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,56 @@ +package com.lemans.ds.attribute + +import static com.lemans.ds.rabbit.QueueRequestType.ATTRIBUTE_VALUE_CREATE +import static com.lemans.ds.rabbit.QueueRequestType.ATTRIBUTE_VALUE_DELETE +import static com.lemans.ds.rabbit.QueueRequestType.ATTRIBUTE_VALUE_UPDATE + +import com.lemans.ds.ReportAndLocaleCommonsController + +class AttributeValueController extends ReportAndLocaleCommonsController { + + def attributeValueService + def attributeValueManagerService + + def index() { + Map criteria = common() + pagination() + renderPaginated attributeValueService.findAttributeValues(criteria) + } + + def show(Integer id) { + renderOne attributeValueService.findAttributeValueById([attributeValueId: id, locale: locale]) + } + + def add() { + Map criteria = request.JSON + common() + AttributeValue attributeValue = attributeValueManagerService.createAttributeValue(criteria, auditUserName) + renderMQObject(attributeValue, ATTRIBUTE_VALUE_CREATE) + } + + def update(Integer id) { + Map criteria = request.JSON + [attributeValueId: id, locale: locale] + if (isDefaultLocale()) { + AttributeValue attributeValue = attributeValueManagerService.updateAttributeValue(criteria, auditUserName) + renderMQObject(attributeValue, ATTRIBUTE_VALUE_UPDATE) + } else { + AttributeValueLocale attributeValueLocale = attributeValueManagerService.updateAttributeValueWithLocale(criteria, auditUserName) + renderObject(attributeValueLocale, attributeValueLocale?.attributeValueId) + } + } + + def remove(Integer id) { + AttributeValue attributeValue + if (id != null) { + attributeValue = attributeValueManagerService.deleteAttributeValue(id, auditUserName) + } + renderMQDelete(attributeValue, ATTRIBUTE_VALUE_DELETE) + } + + def attributeValuesByAttributeNameIdInCategoryContext(Integer attributeNameId) { + Map data = attributeValueService.findAttributeValuesByAttributeNameId(attributeNameId) + data.results ? renderOne(data.results) : renderEmptyResults() + } + + private void renderEmptyResults() { + render(toJson([results: []])) + } +} Index: grails-app/controllers/com/lemans/ds/category/CategoryAttributeController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/category/CategoryAttributeController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/category/CategoryAttributeController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,74 @@ +package com.lemans.ds.category + + +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_ATTRIBUTE_CREATE +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_ATTRIBUTE_DELETE +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_ATTRIBUTE_MOVE +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_ATTRIBUTE_UPDATE +import org.apache.commons.collections.CollectionUtils + +import com.lemans.ds.MoveCommand +import com.lemans.ds.ReportAndLocaleCommonsController + +class CategoryAttributeController extends ReportAndLocaleCommonsController { + + def categoryAttributeService + def categoryAttributeManagerService + + private static final List FLAG_FIELDS = ['isRequired', 'allowMultipleValues', 'isDropDown', 'isHidden', 'isKeyAttribute'] + + def index(Integer catalogInstanceId, Integer categoryId) { + Map criteria = common() + pagination() + [catalogInstanceId: catalogInstanceId, categoryId: categoryId] + renderPaginated categoryAttributeService.findCategoryAttributeByCategory(criteria) + } + + def showByCompositeKey(Integer attributeNameId) { + Map criteria = [locale: locale, attributeNameId: attributeNameId] + filters(['catalogInstanceId', 'categoryId']) + renderOne categoryAttributeService.findCategoryAttributeByCompositKey(criteria) + } + + def add(Integer catalogInstanceId, Integer categoryId) { + Map values = request.JSON + [catalogInstanceId: catalogInstanceId, categoryId: categoryId] + CategoryAttribute categoryAttribute = categoryAttributeManagerService.createCategoryAttribute(values, auditUserName) + categoryAttributeManagerService.sequence([id: categoryAttribute.id], auditUserName) + renderMQObject(categoryAttribute, CATEGORY_ATTRIBUTE_CREATE) + } + + def update() { + Map values = request.JSON + [locale: locale] + Map criteria = filters(['catalogInstanceId', 'categoryId', 'attributeNameId']) + if (isDefaultLocale() || CollectionUtils.containsAny(values.keySet(), FLAG_FIELDS)) { + CategoryAttribute categoryAttribute = categoryAttributeManagerService.updateCategoryAttribute(values, criteria, auditUserName) + renderMQObject(categoryAttribute, CATEGORY_ATTRIBUTE_UPDATE) + } else { + CategoryAttributeLocale categoryAttributeLocale = categoryAttributeManagerService.updateCategoryAttributeWithLocale(values, criteria, auditUserName) + renderObject(categoryAttributeLocale, categoryAttributeLocale?.categoryAttributeId) + } + } + + def move() { + Map values = request.JSON + MoveCommand cmd = new MoveCommand(values) + if (cmd.validate()) { + Map moveCriteria = values + filters(['catalogInstanceId', 'categoryId', 'attributeNameId']) + CategoryAttribute categoryAttribute = categoryAttributeManagerService.moveCategoryAttribute(moveCriteria, auditUserName) + renderMQObjectSpecial( + categoryAttribute, + CATEGORY_ATTRIBUTE_MOVE, + moveCriteria + [appSecurityUser: auditUserName] + ) + } else { + renderObject cmd + } + } + + def remove(Integer version) { + Map criteria = filters(['catalogInstanceId', 'categoryId', 'attributeNameId']) + CategoryAttribute categoryAttribute = categoryAttributeManagerService.deleteCategoryAttribute(criteria, auditUserName, version) + renderMQDelete(categoryAttribute, CATEGORY_ATTRIBUTE_DELETE) + } + + protected show(Integer id) { + renderOne categoryAttributeService.findCategoryAttributeById([categoryAttributeId: id, locale: locale]) + } +} Index: grails-app/controllers/com/lemans/ds/category/CategoryAttributeValueController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/category/CategoryAttributeValueController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/category/CategoryAttributeValueController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,93 @@ +package com.lemans.ds.category + +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_ATTRIBUTE_VALUE_CREATE +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_ATTRIBUTE_VALUE_DELETE +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_ATTRIBUTE_VALUE_MOVE +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_ATTRIBUTE_VALUE_UPDATE + +import com.lemans.ds.MoveCommand +import com.lemans.ds.ReportAndLocaleCommonsController +import com.lemans.ds.rabbit.RequestPayload +import grails.transaction.Transactional + +class CategoryAttributeValueController extends ReportAndLocaleCommonsController { + + def categoryAttributeValueService + def categoryAttributeValueManagerService + + def index() { + Map criteria = common() + filters(['catalogInstanceId', 'categoryId', 'attributeNameId']) + renderPaginated categoryAttributeValueService.findCategoryAttributeValueByCriteria(criteria) + } + + def showByCompositeKey() { + Map criteria = common() + filters(['catalogInstanceId', 'categoryId', 'attributeNameId', 'attributeValueId']) + renderOne categoryAttributeValueService.findCategoryAttributeValueByCompositKey(criteria) + } + + @Transactional + def add(Integer catalogInstanceId, Integer categoryId, Integer attributeNameId) { + Map values = request.JSON + [catalogInstanceId: catalogInstanceId, categoryId: categoryId, attributeNameId: attributeNameId] + CategoryAttributeValue categoryAttributeValue = categoryAttributeValueManagerService.createCategoryAttributeValue(values, auditUserName) + categoryAttributeValueManagerService.sequence([sourceId: categoryAttributeValue.id], auditUserName) + renderMQObject(categoryAttributeValue, CATEGORY_ATTRIBUTE_VALUE_CREATE) + } + + def update(Integer catalogInstanceId, Integer categoryId, Integer attributeNameId, Integer attributeValueId) { + Map values = request.JSON + [locale: locale, catalogInstanceId: catalogInstanceId, + categoryId: categoryId, attributeNameId: attributeNameId, + attributeValueId: attributeValueId] + if (isDefaultLocale()) { + CategoryAttributeValue categoryAttributeValue = categoryAttributeValueManagerService.updateCategoryAttributeValue(values, auditUserName) + renderMQObject(categoryAttributeValue, CATEGORY_ATTRIBUTE_VALUE_UPDATE) + } else { + CategoryAttributeValueLocale categoryAttributeValueLocale = categoryAttributeValueManagerService.updateCategoryAttributeValueWithLocale(values, auditUserName) + renderObject(categoryAttributeValueLocale, categoryAttributeValueLocale?.categoryAttributeValueId) + } + } + + def remove(Integer version) { + Map criteria = [ + catalogInstanceId: params.catalogInstanceId, + categoryId: params.categoryId, + attributeNameId: params.attributeNameId, + attributeValueId: params.attributeValueId + ] + CategoryAttributeValue categoryAttributeValue = categoryAttributeValueManagerService.deleteCategoryAttributeValue(criteria, auditUserName, version) + renderMQDelete(categoryAttributeValue, CATEGORY_ATTRIBUTE_VALUE_DELETE) + } + + def move() { + Map values = request.JSON + Map criteria = [ + catalogInstanceId: params.catalogInstanceId, + categoryId: params.categoryId, + attributeNameId: params.attributeNameId, + attributeValueId: params.attributeValueId, + ] + MoveCommand cmd = new MoveCommand(values) + if (cmd.validate()) { + Map moveCriteria = values + criteria + Map categoryAttributeValue = categoryAttributeValueManagerService.moveCategoryAttributeValue(moveCriteria, auditUserName) + if (categoryAttributeValue) { + if (usToEuSyncToggle == true) { + rabbitMessageService.publishToUsTOEuQueue( + new RequestPayload( + requestType: CATEGORY_ATTRIBUTE_VALUE_MOVE, + id: moveCriteria.sourceId, + body: moveCriteria + [appSecurityUser: auditUserName] + )) + } + show(categoryAttributeValue.categoryAttributeValueId) + } else { + notFound() + } + } else { + renderObject cmd + } + } + + protected show(Integer id) { + renderOne categoryAttributeValueService.findCategoryAttributeValueById([categoryAttributeValueId: id, locale: locale]) + } +} Index: grails-app/controllers/com/lemans/ds/category/CategoryController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/category/CategoryController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/category/CategoryController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,81 @@ +package com.lemans.ds.category + +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_CREATE +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_DELETE +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_MOVE +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_UPDATE + +import com.lemans.ds.MoveCategoryCommand +import com.lemans.ds.ReportAndLocaleCommonsController +import groovy.json.JsonSlurper +import org.json.XML + +class CategoryController extends ReportAndLocaleCommonsController { + + def categoryService + def categoryManagerService + + def index(Integer catalogInstanceId, String categoryId) { + Map criteria = common() + [catalogInstanceId: catalogInstanceId, categoryId: categoryId] + List results = categoryService.findCategories(criteria) + results ? renderMany(results) : notFound() + } + + def show(Integer id) { + Map criteria = common() + [catalogInstanceId: params.catalogInstanceId, id: id] + renderOne categoryService.findCategoryById(criteria) + } + + def details(Integer catalogInstanceId, Integer categoryId) { + String detailsXML = categoryService.categoryDetails([catalogInstanceId: catalogInstanceId, id: categoryId, locale: locale]) + if (detailsXML) { + renderDetails(XML.toJSONObject(detailsXML)?.category?.toString()) + } else { + notFound() + } + } + + def add(Integer catalogInstanceId) { + Map values = request.JSON + [catalogInstanceId: catalogInstanceId] + Category category = categoryManagerService.createCategory(values, auditUserName) + sequence(category) + renderMQObject(category, CATEGORY_CREATE) + } + + def update(Integer catalogInstanceId, Integer id) { + Map values = request.JSON + if (isDefaultLocale()) { + Category category = categoryManagerService.updateCategory(values, catalogInstanceId, id, auditUserName) + sequence(category) + renderMQObject(category, CATEGORY_UPDATE) + } else { + CategoryLocale categoryLocale = categoryManagerService.updateCategoryLocale(values + [locale: locale], + catalogInstanceId, id, auditUserName) + renderObject(categoryLocale, categoryLocale?.categoryId) + } + } + + def move(Integer catalogInstanceId, Integer id) { + Map values = request.JSON + MoveCategoryCommand cmd = new MoveCategoryCommand(values) + if (cmd.validate()) { + Category category = categoryManagerService.moveCategory(values + [catalogInstanceId: catalogInstanceId, id: id], auditUserName) + sequence(category) + renderMQObject(category, CATEGORY_MOVE) + } else { renderObject cmd } + } + + def remove(Integer catalogInstanceId, Integer id, Integer version) { + Category category = categoryManagerService.deleteCategory(catalogInstanceId, id, auditUserName, version) + sequence(category) + renderMQDelete(category, CATEGORY_DELETE) + } + + private void sequence(Category category) { + if (category && !category.hasErrors()) { categoryManagerService.sequence(category) } + } + + private void renderDetails(String categoryDetails) { + render toJson([results: new JsonSlurper().parseText(categoryDetails)]) + } +} Index: grails-app/controllers/com/lemans/ds/category/CategoryPartController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/category/CategoryPartController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/category/CategoryPartController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,18 @@ +package com.lemans.ds.category + +import com.lemans.ds.ReportAndLocaleCommonsController + +class CategoryPartController extends ReportAndLocaleCommonsController { + + def partManagerService + + def addOrRemove(Integer catalogInstanceId, Integer categoryId) { + Map values = request.JSON + [catalogInstanceId: catalogInstanceId, categoryId: categoryId] + if (values.action == 'add' || values.action == 'remove') { + if (values.partNumbers && !values.partNumbers?.isEmpty()) { + Map data = partManagerService.addOrRemoveParts(values, auditUserName) + data.errors ? renderErrors(data.errors) : render(toJson([results: []])) + } else { renderErrors(['No part selected']) } + } else { renderErrors(['action must either be add or remove']) } + } +} Index: grails-app/controllers/com/lemans/ds/category/CategorySubComCodeController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/category/CategorySubComCodeController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/category/CategorySubComCodeController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,35 @@ +package com.lemans.ds.category + +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_SUB_COM_CODE_CREATE +import static com.lemans.ds.rabbit.QueueRequestType.CATEGORY_SUB_COM_CODE_DELETE + +import com.lemans.ds.ReportAndLocaleCommonsController + +class CategorySubComCodeController extends ReportAndLocaleCommonsController { + + def categorySubComCodeService + def categorySubComCodeManagerService + + def index() { + renderMany categorySubComCodeService.findCategorySubComCodes(common() + category()) + } + + def show(Integer subComCodeId) { + renderOne categorySubComCodeService.findCategorySubComCode(common() + category() + [subComCodeId: subComCodeId]) + } + + def add() { + Map values = request.JSON + category() + CategorySubComCode categorySubComCode = categorySubComCodeManagerService.addSubComCode(values, auditUserName) + renderMQObject(categorySubComCode, CATEGORY_SUB_COM_CODE_CREATE, categorySubComCode?.subComCodeId) + } + + def remove(Integer subComCodeId) { + CategorySubComCode subComCode = categorySubComCodeManagerService.removeSubComCode(category(), subComCodeId, auditUserName) + renderMQDelete(subComCode, CATEGORY_SUB_COM_CODE_DELETE) + } + + private Map category() { + [catalogInstanceId: params.int('catalogInstanceId'), categoryId: params.int('categoryId')] + } +} Index: grails-app/controllers/com/lemans/ds/explosion/ExplosionDiagramController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/explosion/ExplosionDiagramController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/explosion/ExplosionDiagramController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,31 @@ +package com.lemans.ds.explosion + +import com.lemans.LemansApiController + +class ExplosionDiagramController extends LemansApiController { + + def explosionDiagramService + + def explosionDiagramManagerService + + def index() { + Map criteria = common() + pagination() + renderPaginated explosionDiagramService.findExplosionDiagrams(criteria) + } + + def show(Integer explosionDiagramId) { + renderOne explosionDiagramService.findExplosionDiagram(explosionDiagramId) + } + + def add() { + renderObject explosionDiagramManagerService.addExplosionDiagram(request.JSON, auditUserName) + } + + def update(Integer explosionDiagramId) { + renderObject explosionDiagramManagerService.updateExplosionDiagram(request.JSON, explosionDiagramId, auditUserName) + } + + def delete(Integer explosionDiagramId) { + renderDelete explosionDiagramManagerService.deleteExplosionDiagram(explosionDiagramId, auditUserName) + } +} Index: grails-app/controllers/com/lemans/ds/fitment/MakeController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/fitment/MakeController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/fitment/MakeController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,36 @@ +package com.lemans.ds.fitment + +import com.lemans.LemansApiController + +class MakeController extends LemansApiController { + + def makeService + + def makeManagerService + + def index() { + Map criteria = common() + pagination() + filters(['makeName']) + Map data = makeService.findMakes(criteria) + renderPaginated data + } + + def show(Integer makeId) { + Map data = makeService.findMakeById(makeId) + renderOne data + } + + def add() { + Make make = makeManagerService.createMake(request.JSON, auditUserName) + renderObject(make) + } + + def update(Integer makeId) { + Make make = makeManagerService.updateMake(request.JSON, makeId, auditUserName) + renderObject(make) + } + + def remove(Integer makeId) { + Make make = makeManagerService.deleteMake(makeId, auditUserName) + renderDelete(make) + } +} Index: grails-app/controllers/com/lemans/ds/fitment/ModelController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/fitment/ModelController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/fitment/ModelController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,83 @@ +package com.lemans.ds.fitment + +import com.lemans.LemansApiController +import org.json.JSONObject +import org.json.XML + +class ModelController extends LemansApiController { + + def modelService + + def modelManagerService + + def index(Integer makeId) { + if (makeId) { + renderModelDetails(modelService.findModelDetails(makeId)) + } else { notFound([[type: 'error', text: 'Make is required']]) } + } + + def show(Integer modelId) { + String modelDetails = modelService.findModelDetails(params.int('makeId'), modelId) + if (modelDetails) { + JSONObject json = new JSONObject() + json.put('results', XML.toJSONObject(modelDetails).model.getJSONObject(0)) + render contentType: 'application/json', text: json + } else { notFound() } + } + + def add() { + Map values = values() + if (values.operation && values.operation != 'INSERT') { bulkOperation(values) } + else { + Model model = modelManagerService.createModel(values, auditUserName) + renderObject(model) + } + } + + def update(Integer modelId) { + Model model = modelManagerService.updateModel(values(), modelId, auditUserName) + renderObject(model) + } + + def remove() { + Map data = modelManagerService.deleteModel(makeModel(), auditUserName) + if (data.errors) { + if (data.errors.find { it.startsWith('Invalid Model') }) { notFound() } + else { renderErrors(data.errors) } + } else { renderEmpty() } + } + + private void renderModelDetails(String modelDetails) { + if (modelDetails) { + JSONObject json = new JSONObject() + json.put('results', XML.toJSONObject(modelDetails).model) + render contentType: 'application/json', text: json + } else { renderEmptyResults() } + } + + private bulkOperation(values) { + String operation = values.operation + if (operation == 'ADDYEAR' || operation == 'DELETEYEAR' || operation == 'DELETE') { + Map data = modelManagerService.updateMultipleModels(values, auditUserName) + data.errors ? renderErrors(data.errors) : renderEmptyResults() + } else { renderErrors(['operation must be INSERT, ADDYEAR, DELETEYEAR or DELETE']) } + } + + private Map make() { + [makeId: params.int('makeId')] + } + + private Map makeModel() { + make() + [modelId: params.int('modelId')] + } + + private Map values() { + Map values = request.JSON + values.makeId = params.int('makeId') + values + } + + private void renderEmptyResults() { + render(toJson([results: []])) + } +} Index: grails-app/controllers/com/lemans/ds/fitment/ModelYearController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/fitment/ModelYearController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/fitment/ModelYearController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,64 @@ +package com.lemans.ds.fitment + +import com.lemans.LemansApiController + +class ModelYearController extends LemansApiController { + + def modelYearService + + def modelYearManagerService + + def index() { + Map criteria = common() + pagination() + makeModel() + Map data = modelYearService.findYears(criteria) + renderPaginated data + } + + def add() { + Map values = values() + List errors = [] + yearValidations(values, errors) + if (errors) { renderErrors(errors) } + else { values.operation == 'DELETE' ? deleteYears(values) : createModelYearOrYears(values) } + } + + def remove(Integer yearId) { + renderDeleteYear(modelYearManagerService.deleteYear(makeModel() + [yearId: yearId], auditUserName)) + } + + private void yearValidations(Map values, List errors) { + String operation = values.operation + if (!values.years && !values.year) { errors << 'year(s) is required' } + if (operation && !(['INSERT', 'DELETE'].contains(operation))) { errors << 'operation must be INSERT or DELETE' } + } + + private createModelYearOrYears(Map values) { + Map data = modelYearManagerService.createMultipleYears(values, auditUserName) + if (data.errors) { renderErrors(data.errors) } + else { + List modelYears = modelYearService.findModelYearsByYears(params.int('modelId'), values.years) + values.year ? renderOne(modelYears[0]) : renderList(modelYears) + } + } + + private deleteYears(Map values) { + renderDeleteYear(modelYearManagerService.deleteMultipleYears(values, auditUserName)) + } + + private renderDeleteYear(Map data) { + data.errors ? renderErrors(data.errors) : render(toJson([:])) + } + + private Map makeModel() { + [makeId: params.int('makeId'), modelId: params.int('modelId')] + } + + private Map values() { + Map values = request.JSON + values.makeId = params.int('makeId') + values.modelId = params.int('modelId') + Integer year = values.year + if (year) { values.years = [year] } + values + } +} Index: grails-app/controllers/com/lemans/ds/fitment/PartFitmentController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/fitment/PartFitmentController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/fitment/PartFitmentController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,75 @@ +package com.lemans.ds.fitment + +import com.lemans.LemansApiController +import org.json.JSONObject +import org.json.XML + +/** + * Created by vramisetti on 6/12/2017. + */ +class PartFitmentController extends LemansApiController { + + def partFitmentManagerService + + def partFitmentService + + def fitmentsByPart(String partNumber) { + Map data = partFitmentService.findFitmentsByPart([partNumber: partNumber] + common() + pagination()) + if (data.results) { + JSONObject json = new JSONObject() + json.put('results', XML.toJSONObject(data.results).model) + json.put('meta', [totalRecords: data.totalRecords]) + render contentType: 'application/json', text: json + } else { renderEmptyResults() } + } + + def add(String partNumber) { + Map values = request.JSON + values.partNumber = partNumber + String operation = values.operation + if (operation == 'DELETE') { deleteMultipleFitments(values) } + else { createPartFitments(values) } + } + + def delete(String partNumber, Integer fitmentId) { + renderDeleteYear(partFitmentManagerService.deleteFitment(partNumber, fitmentId, auditUserName)) + } + + private deleteMultipleFitments(Map values) { + if (values.partFitmentIds) { renderDeleteYear(partFitmentManagerService.deleteMultipleFitments(values, auditUserName)) } + else { renderErrors(['partFitmentIds are required']) } + } + + private createPartFitments(Map values) { + List errors = [] + Integer modelYearId = values.modelYearId + if (modelYearId) { values.modelYearIds = [modelYearId] } + modelYearValidation(values, errors) + if (errors) { renderErrors(errors) } + else { + Map data = partFitmentManagerService.createFitments(values, auditUserName) + if (data.errors) { renderErrors(data.errors) } + else { + List fitments = partFitmentService.findFitmentsByPartNumberAndModelYearIds(values.partNumber, values.modelYearIds) + if (values.positions && fitments) { + partFitmentManagerService.assignFitmentPositions(values.positions, fitments*.partFitmentId, auditUserName) + } + values.modelYearId ? renderOne(fitments[0]) : renderList(fitments) + } + } + } + + private void modelYearValidation(Map values, List errors) { + String operation = values.operation + if (!values.modelYearId && !values.modelYearIds) { errors << 'modelYears(s) is required' } + if (operation && !(['INSERT'].contains(operation))) { errors << 'operation must be INSERT or DELETE' } + } + + private renderDeleteYear(Map data) { + data.errors ? renderErrors(data.errors) : render(toJson([:])) + } + + private void renderEmptyResults() { + render(toJson([results: [], meta: [totalRecords: 0]])) + } +} Index: grails-app/controllers/com/lemans/ds/fitment/SegmentController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/fitment/SegmentController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/fitment/SegmentController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,33 @@ +package com.lemans.ds.fitment + +import com.lemans.LemansApiController + +class SegmentController extends LemansApiController { + + def segmentService + + def segmentManagerService + + def segments() { + Map criteria = common() + pagination() + Map data = segmentService.findSegments(criteria) + renderPaginated data + } + + def segmentValues(Integer makeId, String segmentCode) { + Map criteria = common() + pagination() + [segmentCode: segmentCode, value: params.value, makeId: makeId] + Map data = segmentService.findSegmentValuesBySegmentCode(criteria) + renderPaginated data + } + + def addUpdateOrDeleteModelSegment(Integer makeId, Integer modelId, String segmentCode) { + Map criteria = [makeId: makeId, modelId: modelId, segmentCode: segmentCode, value: request.JSON?.value] + ModelSegment modelSegment = segmentManagerService.updateOrCreateModelSegment(criteria, auditUserName) + modelSegment ? (modelSegment.hasErrors() ? renderErrors(modelSegment) : renderModelSegment(modelSegment)) : notFound() + } + + private renderModelSegment(ModelSegment modelSegment) { + renderOne([modelSegmentId: modelSegment.id, modelId: modelSegment.model.id, segmentId: modelSegment.segment.id, + value: modelSegment.dateDeleted ? null : modelSegment.segmentValue]) + } +} Index: grails-app/controllers/com/lemans/ds/flag/FlagController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/flag/FlagController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/flag/FlagController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,13 @@ +package com.lemans.ds.flag + +import com.lemans.LemansApiController + +class FlagController extends LemansApiController { + + def flagService + + def index() { + Map criteria = common() + pagination() + filters(['entityClass']) + renderPaginated flagService.findFlags(criteria) + } +} Index: grails-app/controllers/com/lemans/ds/flag/FlagValueController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/flag/FlagValueController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/flag/FlagValueController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,29 @@ +package com.lemans.ds.flag + +import com.lemans.LemansApiController + +class FlagValueController extends LemansApiController { + + def flagValueService + + def flagValueManagerService + + def show() { + renderOne flagValueService.findById(filters(['entityClass', 'entityId', 'flagId'])) + } + + def index() { + Map criteria = common() + pagination() + filters(['entityClass', 'entityId']) + renderPaginated flagValueService.findFlags(criteria) + } + + def addOrDeleteFlagValues() { + Map criteria = request.JSON + filters(['entityClass', 'entityId']) + if (criteria.operation && !(criteria.operation in ['INSERT', 'DELETE'])) { + renderErrors(['operation must either be INSERT or DELETE']) + } else { + Map data = flagValueManagerService.addOrDeleteFlagValues(criteria, auditUserName) + data.errors ? renderErrors(data.errors) : render(toJson([results: []])) + } + } +} Index: grails-app/controllers/com/lemans/ds/locale/BulkLocaleExportController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/locale/BulkLocaleExportController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/locale/BulkLocaleExportController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,36 @@ +package com.lemans.ds.locale + +import com.lemans.ds.ReportAndLocaleCommonsController + +class BulkLocaleExportController extends ReportAndLocaleCommonsController { + + def ssrReporter + + def reportServerExportFolder + + def downloadBulkExportFile(String reportName) { + String url + List urlKeys + + switch (reportName) { + case 'productLocaleExport': + url = '/ProductExport' + urlKeys = ['productId', 'productName', 'categoryId', 'brandId', 'isDigiActive', 'dashboardUrl'] + break + case 'categoryAttributeLocaleExport': + url = '/CategoryAttributeValueExport' + urlKeys = ['categoryId', 'dashboardUrl'] + break + case 'partLocaleExport': + url = '/PartExport' + urlKeys = ['partNumber', 'exportPartList', 'partDescr', 'qPart', 'partStatusId', 'derivedPartStatusId', + 'brandId', 'vendorId', 'vendorPartNumber', 'categoryId', 'productId', 'subComCodeId', + 'isDigiActive', 'modelId', 'startYear', 'endYear', 'attributeNameId', 'attributeValueId', + 'dashboardUrl'] + break + } + Map criteria = [reportFormat: params.reportFormat ?: 'Excel', url: reportServerExportFolder + url] + criteria.urlParams = ssrReporter.createUrlParams(urlKeys, params + [dashboardUrl: params.urlContext]) + generateReport(criteria, response, ssrReporter) + } +} Index: grails-app/controllers/com/lemans/ds/locale/BulkLocaleImportController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/locale/BulkLocaleImportController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/locale/BulkLocaleImportController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,92 @@ +package com.lemans.ds.locale + +import com.lemans.LemansApiController +import com.lemans.ds.importing.BulkImportJob +import com.lemans.ds.importing.LocaleImportProcess + + +class BulkLocaleImportController extends LemansApiController { + + def bulkImportManagerService + + def bulkImportService + + def index() { + Map criteria = common() + pagination() + filters(['importType']) + renderPaginated bulkImportService.findBulkImports(criteria) + } + + def show(Integer importFileId) { + renderOne bulkImportService.findLocaleImportProcessById([localeImportProcessId: importFileId]) + } + + def add() { + Map values = request.JSON + List errors = validate(values) + if (errors) { renderErrors(errors) } + else { + LocaleImportProcess localeImportProcess = bulkImportManagerService.addLocaleImportProcessFile(values, auditUserName) + renderObject localeImportProcess + Integer processId = localeImportProcess.id + if (processId) { + BulkImportJob.triggerNow(['processId': processId]) + } + } + } + + def remove(Integer importFileId) { + LocaleImportProcess localeImportProcess = bulkImportManagerService.deleteLocaleImportProcess(importFileId, auditUserName) + renderDelete localeImportProcess + } + + def download(Integer importFileId, String fileId) { + fileId == 'Original' ? downloadOriginal(importFileId) : downloadReport(importFileId) + } + + private List validate(Map values) { + List errors = [] + if (!values.tempId) { + errors << 'tempId is required' + } + if (!values.importType) { + errors << 'ImportType is required' + } + if (!values.extension) { + errors << 'Extension is required' + } + errors + } + + private downloadOriginal(Integer importFileId) { + Closure file = { Map localeImportProcess -> + bulkImportService.findLocaleImportProcessOriginalFile(localeImportProcess) + } + downloadImportFile(importFileId, file) + } + + private downloadReport(Integer importFileId) { + Closure file = { Map localeImportProcess -> + bulkImportService.findLocaleImportProcessReportFile(localeImportProcess) + } + downloadImportFile(importFileId, file, 'Report.xls') + } + + private downloadImportFile(Integer importFileId, Closure fileToDownload, String fileNameToBeOverwritten = null) { + Map localeImportProcess = bulkImportService.findLocaleImportProcessById([localeImportProcessId: importFileId]) + if (canDownloadFile(fileNameToBeOverwritten, localeImportProcess)) { + File file = fileToDownload(localeImportProcess) + if (file) { + response.setHeader 'Content-disposition', + "attachment;filename=${fileNameToBeOverwritten ?: "${localeImportProcess.originalFileName}.${localeImportProcess.extension}"}" + response.contentType = localeImportProcess?.mimeType + response.outputStream << file.newInputStream() + response.outputStream.flush() + } else { renderErrors(['File could not be found!. Please contact helpdesk']) } + } else { notFound() } + } + + private boolean canDownloadFile(String fileNameToBeOverwritten, Map localeImportProcess) { + localeImportProcess && ((localeImportProcess.originalFileName && !fileNameToBeOverwritten) || + (fileNameToBeOverwritten.equalsIgnoreCase('Report.xls') && localeImportProcess.status == 'Partial Success')) + } +} Index: grails-app/controllers/com/lemans/ds/part/PartAssociationController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/part/PartAssociationController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/part/PartAssociationController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,46 @@ +package com.lemans.ds.part + +import com.lemans.LemansApiController + +class PartAssociationController extends LemansApiController { + + def partAssociationService + + def partAssociationManagerService + + def relatedParts(String partNumber) { + Map criteria = common() + pagination() + [partNumber: partNumber] + Map data = partAssociationService.findRelatedParts(criteria) + renderPaginated data + } + + def referralParts(String partNumber) { + Map criteria = common() + pagination() + ['relatedPartNumber': partNumber] + Map data = partAssociationService.findReferralParts(criteria) + renderPaginated data + } + + def show(Integer id) { + Map criteria = common() + pagination() + [partAssociationId: id] + Map data = partAssociationService.findPartAssociation(criteria) + renderOne data + } + + def add(String partNumber) { + Map input = request.JSON + input.relatedPartNumber = input.relatedPartNumber.toString().replaceAll('[^a-zA-Z0-9]', '') + PartAssociation partAssociation = partAssociationManagerService.addPartAssociation(partNumber, input, auditUserName) + renderObject partAssociation + } + + def update(Integer id) { + Map input = request.JSON + PartAssociation partAssociation = partAssociationManagerService.updatePartAssociation(id, input, auditUserName) + renderObject partAssociation + } + + def remove(Integer id) { + PartAssociation partAssociation = partAssociationManagerService.deletePartAssociation(id, auditUserName) + renderDelete partAssociation + } +} Index: grails-app/controllers/com/lemans/ds/part/PartAttributeController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/part/PartAttributeController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/part/PartAttributeController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,47 @@ +package com.lemans.ds.part + +import com.lemans.ds.ReportAndLocaleCommonsController +import groovy.json.JsonSlurper +import org.json.XML + +class PartAttributeController extends ReportAndLocaleCommonsController { + + def partAttributeService + def partAttributeManagerService + + def add(String partNumber, Integer attributeNameId) { + Map criteria = request.JSON + [partNumber: partNumber, attributeNameId: attributeNameId] + renderResults partAttributeManagerService.addSinglePartAttributeRelation(criteria, auditUserName) + } + + def remove() { + Map criteria = filters(['partNumber', 'attributeNameId', 'attributeValueId']) + renderResults partAttributeManagerService.removeSinglePartAttributeRelation(criteria, auditUserName) + } + + def bulkAddOrRemoveOperation() { + renderResults partAttributeManagerService.bulkAddOrRemove(request.JSON, auditUserName) + } + + def bulkReplaceOperation(String partNumber, Integer attributeNameId) { + Map values = request.JSON + [partNumber: partNumber, attributeNameId: attributeNameId] + renderResults partAttributeManagerService.bulkReplaceAttributeValueRelation(values, auditUserName) + } + + def categoryPartDetails(Integer catalogInstanceId, Integer categoryId) { + List partNumbers = request.JSON?.partNumber + Map criteria = [catalogInstanceId: catalogInstanceId, id: categoryId, locale: locale] + String detailsXML = isDefaultLocale() ? partAttributeService.partAttributeDetails(criteria, partNumbers) + : partAttributeService.partAttributeLocaleDetails(criteria, partNumbers) + detailsXML ? render(toJson([results: new JsonSlurper().parseText(XML.toJSONObject(detailsXML)?.category?.toString())])) : notFound() + } + + private void renderResults(Map data) { + if (data.messages.find { it.type == 'error' }) { + response.status = 400 + render toJson(data) + } else { + render toJson([results: [:]]) + } + } +} Index: grails-app/controllers/com/lemans/ds/part/PartController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/part/PartController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/part/PartController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,37 @@ +package com.lemans.ds.part + +import com.lemans.ds.ReportAndLocaleCommonsController + +class PartController extends ReportAndLocaleCommonsController { + + def partService + + def partManagerService + + def index(PartSearchCommand cmd) { + List errors = [] + if (!params.pageSize) { errors << [type: 'error', text: 'pageSize is required'] } + if (errors) { + response.status = 400 + renderMessages(errors) + } else { + Map criteria = common() + pagination() + cmd.properties + renderPaginated partService.findParts(criteria) + } + } + + def show(String partNumber) { + Map criteria = [partNumber: partNumber, locale: locale] + renderOne(isDefaultLocale() ? partService.findPartByPartNumber(criteria) : partService.findPartsByLocale(criteria)) + } + + def update(String partNumber) { + Map input = request.JSON + [locale: locale] + if (input.primaryMediaId || isDefaultLocale()) { + renderObject partManagerService.updatePart(input, partNumber.toUpperCase(), auditUserName) + } else { + PartLocale partLocale = partManagerService.updatePartLocale(input, partNumber.toUpperCase(), auditUserName) + renderObject(partLocale, partLocale?.partNumber) + } + } +} Index: grails-app/controllers/com/lemans/ds/part/PartSearchCommand.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/part/PartSearchCommand.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/part/PartSearchCommand.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,35 @@ +package com.lemans.ds.part + +import grails.validation.Validateable + + +class PartSearchCommand implements Validateable { + + String q + String partNumber + String partStatusCode + String partDescr + String brandCode + String brandName + String vendorId + String vendorName + String vendorPartNumber + String subComCode + String productId + Integer categoryId + + static constraints = { + q nullable: true, minSize: 2 + partNumber nullable: true, minSize: 2 + partStatusCode nullable: true, minSize: 1 + partDescr nullable: true, minSize: 2 + brandCode nullable: true, minSize: 2 + brandName nullable: true, minSize: 2 + vendorId nullable: true, minSize: 2 + vendorName nullable: true, minSize: 2 + vendorPartNumber nullable: true, minSize: 2 + subComCode nullable: true + productId nullable: true + categoryId nullable: true + } +} Index: grails-app/controllers/com/lemans/ds/product/CategoryProductController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/product/CategoryProductController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/product/CategoryProductController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,18 @@ +package com.lemans.ds.product + +import com.lemans.LemansApiController + +class CategoryProductController extends LemansApiController { + + def categoryProductManagerService + + def addOrRemoveAssociation(Integer catalogInstanceId, Integer categoryId) { + Map values = request.JSON + [catalogInstanceId: catalogInstanceId, categoryId: categoryId] + if (values.operation == 'INSERT' || values.operation == 'DELETE') { + Map data = categoryProductManagerService.addOrRemoveCategoryProduct(values, auditUserName) + data.errors ? renderErrors(data.errors) : render(toJson([results: [:]])) + } else { + renderErrors(['action must either be add or remove']) + } + } +} Index: grails-app/controllers/com/lemans/ds/product/ProductAssociationController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/product/ProductAssociationController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/product/ProductAssociationController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,46 @@ +package com.lemans.ds.product + +import com.lemans.LemansApiController + +class ProductAssociationController extends LemansApiController { + + def productAssociationService + + def productAssociationManagerService + + def relatedProducts(Integer productId) { + Map criteria = common() + pagination() + [productId: productId] + Map data = productAssociationService.findRelatedProducts(criteria) + renderPaginated data + } + + def referralProducts(Integer productId) { + Map criteria = common() + pagination() + [relatedProductId: productId] + Map data = productAssociationService.findReferralProducts(criteria) + renderPaginated data + } + + def show(Integer id) { + Map criteria = common() + pagination() + [productAssociationId: id] + Map data = productAssociationService.findProductAssociation(criteria) + renderOne data + } + + def add(Integer productId) { + Map input = request.JSON + input.relatedProductId = input.relatedProductId.toString().replaceAll('[^a-zA-Z0-9]', '') + ProductAssociation productAssociation = productAssociationManagerService.addProductAssociation(productId, input, auditUserName) + renderObject productAssociation + } + + def update(Integer id) { + Map input = request.JSON + ProductAssociation productAssociation = productAssociationManagerService.updateProductAssociation(id, input, auditUserName) + renderObject productAssociation + } + + def remove(Integer id) { + ProductAssociation productAssociation = productAssociationManagerService.deleteProductAssociation(id, auditUserName) + renderDelete productAssociation + } +} Index: grails-app/controllers/com/lemans/ds/product/ProductController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/product/ProductController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/product/ProductController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,49 @@ +package com.lemans.ds.product + +import com.lemans.ds.ReportAndLocaleCommonsController + +class ProductController extends ReportAndLocaleCommonsController { + + def productService + + def productManagerService + + def index() { + Map criteria = common() + pagination() + catalog() + filters(['brandId', 'brandCode', 'isDigiActive', + 'productName', 'productNameLocale', 'productId', 'flagId', 'mode']) + if (params.categoryId) { criteria.categoryId = params.list('categoryId') } + renderPaginated productService.findProducts(criteria) + } + + def show(Integer productId) { + Map criteria = common() + catalog() + [productId: productId, entityClass: 'Product'] + renderOne productService.findProductById(criteria) + } + + def add() { + Map values = request.JSON + catalog() + common() + if (!(values.isDigiActive)) { values.put('isDigiActive', '0') } + renderObject productManagerService.createProduct(values, auditUserName) + } + + def update(Integer catalogInstanceId, Integer productId) { + Map values = request.JSON + common() + [productId: productId] + if (isDefaultLocale()) { + renderObject productManagerService.updateProduct(values, catalogInstanceId, auditUserName) + } else { + ProductLocale productLocale = productManagerService.updateProductWithLocale(values, catalogInstanceId, auditUserName) + renderObject(productLocale, productLocale?.productId) + } + } + + def remove(Integer catalogInstanceId, Integer productId) { + renderDelete productManagerService.deleteProduct(catalogInstanceId, productId, auditUserName) + } + + def splitValidation(Integer productId) { + Map data = productService.splitValidationForProduct(productId) + render toJson([results: data.results]) + } + + private Map catalog() { [catalogInstanceId: params.int('catalogInstanceId')] } +} Index: grails-app/controllers/com/lemans/ds/product/ProductFeatureController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/product/ProductFeatureController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/product/ProductFeatureController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,61 @@ +package com.lemans.ds.product + +import com.lemans.ds.MoveCommand +import com.lemans.ds.ReportAndLocaleCommonsController + +class ProductFeatureController extends ReportAndLocaleCommonsController { + + def productFeatureService + + def productFeatureManagerService + + def index() { + Map criteria = common() + pagination() + product() + renderPaginated productFeatureService.findProductFeatures(criteria) + } + + def show(Integer productFeatureId) { + Map criteria = product() + [productFeatureId: productFeatureId, locale: locale] + renderOne productFeatureService.findProductFeatureById(criteria) + } + + def add() { + def body = request.JSON + List features = body.features ?: [body] + Map values = [features: features] + product() + Map data = productFeatureManagerService.createProductFeatures(values, auditUserName) + List messages = data.messages + if (messages) { + response.status = 400 + renderMessages(data.messages) + } else { + messages = [infoMessage("Created ${features.size()} Product Feature(s)")] + } + render toJson(messages: messages, results: data.results) + } + + def update(Integer productFeatureId) { + Map values = request.JSON + common() + product() + [productFeatureId: productFeatureId] + if (isDefaultLocale()) { + renderObject productFeatureManagerService.updateProductFeature(values, auditUserName) + } else { + ProductFeatureLocale featureLocale = productFeatureManagerService.updateProductFeatureWithLocale(values, auditUserName) + renderObject(featureLocale, featureLocale?.productFeatureId) + } + } + + def remove(Integer productId, Integer productFeatureId) { + renderDelete productFeatureManagerService.deleteProductFeature(productId, productFeatureId, auditUserName) + } + + def move(Integer productFeatureId) { + Map values = request.JSON + MoveCommand cmd = new MoveCommand(values) + Map criteria = values + product() + [productFeatureId: productFeatureId] + renderObject(cmd.validate() ? productFeatureManagerService.moveProductFeature(criteria, auditUserName) : cmd) + } + + private Map product() { + [catalogInstanceId: params.int('catalogInstanceId'), productId: params.int('productId')] + } +} Index: grails-app/controllers/com/lemans/ds/product/ProductMerchandiseController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/product/ProductMerchandiseController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/product/ProductMerchandiseController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,37 @@ +package com.lemans.ds.product + +import com.lemans.ds.ReportAndLocaleCommonsController +import groovy.json.JsonSlurper +import org.json.XML + +class ProductMerchandiseController extends ReportAndLocaleCommonsController { + + def productMerchandiseService + def productMerchandiseManagerService + + def show(Integer productCategoryAttributeId) { + Map criteria = common() + [productCategoryAttributeId: productCategoryAttributeId] + renderOne productMerchandiseService.findCategoryAttributeById(criteria) + } + + def getMerchandiseProductAttributeDetails() { + Map values = request.JSON + common() + pagination() + product() + Map criteria = [catalogInstanceId: values.catalogInstanceId, productId: values.productId, locale: locale] + String detailsXML = isDefaultLocale() ? productMerchandiseService.findProductAttributeMerchandiseDetails(criteria) : + productMerchandiseService.findProductAttributeMerchandiseLocaleDetails(criteria) + if (detailsXML) { + render toJson([results: new JsonSlurper().parseText(XML.toJSONObject(detailsXML)?.attribute?.toString())]) + } else { + notFound() + } + } + + def update(Integer productId, Integer categoryAttributeId) { + Map values = request.JSON + common() + [productId: productId, categoryAttributeId: categoryAttributeId] + renderObject productMerchandiseManagerService.updateProductCategoryAttribute(values, auditUserName) + } + + private Map product() { + [catalogInstanceId: params.int('catalogInstanceId'), productId: params.int('productId')] + } +} Index: grails-app/controllers/com/lemans/ds/product/ProductPartController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/product/ProductPartController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/product/ProductPartController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,68 @@ +package com.lemans.ds.product + +import com.lemans.ds.ReportAndLocaleCommonsController +import groovy.json.JsonSlurper +import org.json.XML + + +class ProductPartController extends ReportAndLocaleCommonsController { + + def productPartService + + def productPartManagerService + + def productMerchandiseService + + def index(Integer productId) { + Map criteria = common() + pagination() + catalog() + [productId: productId, attribute: params.attribute] + String detailsXML + if (criteria.attribute) { + detailsXML = productMerchandiseService.findProductPartMerchandiseDetails(criteria) + if (detailsXML) { + renderDetails(XML.toJSONObject(detailsXML)?.product?.toString()) + } else if (criteria.locale == Locale.default || !validateLocale(criteria.locale)) { + notFound() + } + } + else { + Map data = productPartService.findProductParts(criteria) + renderPaginated data + } + } + + def show(Integer productId, String partNumber) { + Map criteria = common() + catalog() + [productId: productId, partNumber: partNumber] + Map data = productPartService.findProductPartByComposite(criteria) + renderOne data + } + + def bulkAddOrRemoveOperation(Integer productId) { + Map values = request.JSON + values.productId = productId + values << catalog() + Map data = productPartManagerService.bulkAddOrRemove(values, auditUserName) + renderPersistResults(data) + } + + private void renderDetails(String categoryDetails) { + Map details = new JsonSlurper().parseText(categoryDetails) + Map data = [results: details] + render toJson(data) + } + + def remove() { + Map criteria = [ + catalogInstanceId: params.catalogInstanceId, + partNumber: params.partNumber, + productId: params.productId + ] + Map data = productPartManagerService.removeSingleProductPartRelation(criteria, auditUserName) + renderPersistResults(data) + } + + private void renderPersistResults(Map data) { + data.errors ? renderErrors(data.errors) : render(toJson([results: [:]])) + } + + private Map catalog() { [catalogInstanceId: params.int('catalogInstanceId')] } +} Index: grails-app/controllers/com/lemans/ds/publicationcategory/ProductPublicationCategoryController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/publicationcategory/ProductPublicationCategoryController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/publicationcategory/ProductPublicationCategoryController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,32 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.LemansApiController + +class ProductPublicationCategoryController extends LemansApiController { + + def productPublicationCategoryService + + def productPublicationCategoryManagerService + + def byCategoryId(Integer categoryId) { + Map criteria = common() + pagination() + [categoryId: categoryId, isDigiActive: 1] + renderPaginated productPublicationCategoryService.productPublicationCategoriesByCategoryId(criteria) + } + + def show(Integer productPublicationCategoryId) { + renderOne productPublicationCategoryService.productPublicationCategory(productPublicationCategoryId) + } + + def byProductId(Integer productId) { + Map criteria = common() + pagination() + [productId: productId, isDigiActive: 1] + renderPaginated productPublicationCategoryService.productPublicationCategoriesByProductId(criteria) + } + + def add(Integer categoryId) { + renderObject productPublicationCategoryManagerService.add(request.JSON, categoryId, auditUserName) + } + + def delete(Integer categoryId, Integer productId) { + renderDelete productPublicationCategoryManagerService.deleteProductPublicationCategory(categoryId, productId, auditUserName) + } +} Index: grails-app/controllers/com/lemans/ds/publicationcategory/PublicationCategoryAttributeController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/publicationcategory/PublicationCategoryAttributeController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/publicationcategory/PublicationCategoryAttributeController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,46 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.LemansApiController +import com.lemans.ds.MoveCommand + +class PublicationCategoryAttributeController extends LemansApiController { + + def publicationCategoryAttributeService + + def publicationCategoryAttributeManagerService + + def index(Integer categoryId) { + Map criteria = common() + pagination() + [publicationCategoryId: categoryId] + renderPaginated publicationCategoryAttributeService.findAllAttributes(criteria) + } + + def attributeOptions(Integer categoryId) { + renderPaginated publicationCategoryAttributeService.attributeOptions(categoryId) + } + + def showByCompositeKey(Integer categoryId, Integer attributeNameId) { + renderOne publicationCategoryAttributeService.findPublicationCategoryAttribute(categoryId, attributeNameId) + } + + def add(Integer categoryId) { + renderObject publicationCategoryAttributeManagerService.addAttribute(request.JSON + [publicationCategoryId: categoryId], auditUserName) + } + + def delete(Integer categoryId, Integer attributeNameId) { + renderDelete publicationCategoryAttributeManagerService.deleteAttribute(categoryId, attributeNameId, auditUserName) + } + + def show(Integer publicationCategoryAttributeId) { + renderOne publicationCategoryAttributeService.findById(publicationCategoryAttributeId) + } + + def move(Integer categoryId, Integer attributeNameId) { + Map input = request.JSON + [publicationCategoryId: categoryId, attributeNameId: attributeNameId] + MoveCommand cmd = new MoveCommand([position: input.position, targetId: input.targetId]) + if (cmd.validate()) { + renderObject publicationCategoryAttributeManagerService.moveAttribute(input, auditUserName) + } else { + renderObject cmd + } + } +} Index: grails-app/controllers/com/lemans/ds/publicationcategory/PublicationCategoryController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/publicationcategory/PublicationCategoryController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/publicationcategory/PublicationCategoryController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,42 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.LemansApiController +import com.lemans.ds.MoveCategoryCommand + +class PublicationCategoryController extends LemansApiController { + + def publicationCategoryService + + def publicationCategoryManagerService + + def index() { + Map criteria = common() + pagination() + renderPaginated publicationCategoryService.findPublicationCategories(criteria) + } + + def show(Integer categoryId) { + renderOne publicationCategoryService.findPublicationCategoryById(categoryId) + } + + def add() { + renderObject publicationCategoryManagerService.createCategory(request.JSON, auditUserName) + } + + def update(Integer categoryId) { + renderObject publicationCategoryManagerService.updateCategory(request.JSON, categoryId, auditUserName) + } + + def delete(Integer categoryId, Integer version) { + renderDelete publicationCategoryManagerService.deleteCategory(categoryId, version, auditUserName) + } + + def move(Integer categoryId) { + Map input = request.JSON + MoveCategoryCommand cmd = new MoveCategoryCommand(input) + if (cmd.validate()) { + renderObject publicationCategoryManagerService.movePublicationCategory(input, categoryId, auditUserName) + } else { + renderObject cmd + } + } +} Index: grails-app/controllers/com/lemans/ds/search/BrandSearchController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/search/BrandSearchController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/search/BrandSearchController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,14 @@ +package com.lemans.ds.search + +import com.lemans.LemansApiController + +class BrandSearchController extends LemansApiController { + + def brandSearchService + + def brandSearch() { + Map criteria = common() + pagination() + [query: params.query, sort: params.sort ?: 'brandId'] + Map data = brandSearchService.searchBrands(criteria) + renderPaginated(data) + } +} Index: grails-app/controllers/com/lemans/ds/search/GenericSearchCommand.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/search/GenericSearchCommand.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/search/GenericSearchCommand.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,72 @@ +package com.lemans.ds.search + +import grails.validation.Validateable + +/** + * Created by vramisetti on 3/9/2017. + */ +class GenericSearchCommand implements Validateable { + + String q + String partNumber + String partStatusCode + String partDescr + String brandCode + String brandName + String vendorId + String vendorName + String vendorPartNumber + String subComCode + String productId + Integer assignableCategoryId + Integer brandId + Integer subComCodeId + Integer attributeNameId + Integer attributeValueId + String assignableProductId + Integer mediaTypeId + String productName + Integer modelId + Integer startYear + Integer endYear + Boolean isDigiActive + String mode + Integer flagId + + static constraints = { + q nullable: true, minSize: 2 + partNumber nullable: true, minSize: 2 + partStatusCode nullable: true, minSize: 1 + partDescr nullable: true, minSize: 2 + brandCode nullable: true, minSize: 2 + brandName nullable: true, minSize: 2 + vendorId nullable: true, minSize: 2 + vendorName nullable: true, minSize: 2 + vendorPartNumber nullable: true, minSize: 2 + subComCode nullable: true + productId nullable: true + attributeNameId nullable: true + attributeValueId nullable: true + modelId nullable: true + startYear nullable: true + endYear nullable: true + assignableCategoryId nullable: true + brandId nullable: true + subComCodeId nullable: true + + assignableProductId nullable: true + + isDigiActive nullable: true + + mode nullable: true + flagId nullable: true + } + + String setPartNumber(String partNumber) { + this.partNumber = partNumber.replaceAll('[^\\p{Alnum}]', '') + } + + String setVendorPartNumber(String vendorPartNumber) { + this.vendorPartNumber = vendorPartNumber.replaceAll('[^\\p{Alnum}]', '') + } +} Index: grails-app/controllers/com/lemans/ds/search/GenericSearchController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/ds/search/GenericSearchController.groovy (revision 0) +++ grails-app/controllers/com/lemans/ds/search/GenericSearchController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,94 @@ +package com.lemans.ds.search + +import com.lemans.LemansApiController +import org.json.JSONObject +import org.json.XML + +class GenericSearchController extends LemansApiController { + + def genericSearchService + + def filterOptions(String searchType, String filterName, GenericSearchCommand cmd) { + String filterType = params.filterType ?: params.searchType + List errors = collectFilterOptionErrors(searchType, filterType, filterName) + if (errors) { renderErrors(errors) } + else { + renderPaginated genericSearchService.filterOptions(searchType, filterType, cmd.properties + [filterName: filterName] + + multiValuedParameters()) + } + } + + def filters(String searchType, String filterType, GenericSearchCommand cmd) { + if (filterType && validFilterType(filterType, searchType)) { + renderXMLasJSON genericSearchService.filters(searchType, filterType, cmd.properties) + } else { renderErrors(['Invalid Filter Type']) } + } + + def search(String searchType, GenericSearchCommand cmd) { + String filterType = params.filterType ?: params.searchType + List errors = collectErrors(filterType, searchType) + if (errors) { renderErrors(errors) } + else { + Map criteria = pagination() + cmd.properties + multiValuedParameters() + if (criteria.mode) { + renderXMLasPaginatedJSON genericSearchService.searchResultsByMode(searchType, filterType, criteria) + } else { + renderPaginated genericSearchService.searchResults(searchType, filterType, criteria) + } + } + } + + private boolean validFilterType(String filterType, String searchType) { + Map acceptableFilterTypes = FilterXmlGenerator.SEARCH_FILTER_TYPE_MAPPING[searchType.toUpperCase()] + acceptableFilterTypes && acceptableFilterTypes[filterType.toUpperCase()] + } + + private boolean validFilterName(String filterType, String searchType, String filterName) { + Map acceptableFilterTypes = FilterXmlGenerator.SEARCH_FILTER_TYPE_MAPPING[searchType.toUpperCase()] + if (acceptableFilterTypes) { + filterName in acceptableFilterTypes[filterType.toUpperCase()] + } + } + + private List collectErrors(String filterType, String searchType) { + List errors = [] + if (!params.pageSize) { errors << 'pageSize is required' } + if (!filterType) { errors << 'filterType is required' } + if ((filterType && !validFilterType(filterType, searchType))) { errors << 'Invalid Filter Type' } + errors + } + + private List collectFilterOptionErrors(String searchType, String filterType, String filterName) { + List errors = [] + if (!filterType) { errors << 'filterType is required' } + if ((filterType && !validFilterType(filterType, searchType))) { errors << 'Invalid Filter Type' } + if (!validFilterName(filterType, searchType, filterName)) { errors << 'Invalid Filter Name' } + errors + } + + private void renderXMLasPaginatedJSON(Map data) { + if (data.totalRecords) { + JSONObject json = new JSONObject() + json.put('meta', [totalRecords: data.totalRecords]) + json.put('results', ((JSONObject) XML.toJSONObject(data.resultXML).get('root')).get('part')) + render contentType: 'application/json', text: json + } else { + render toJson([meta: [totalRecords: 0], results: []]) + } + } + + private void renderXMLasJSON(String xml) { + if (xml && xml != '') { + JSONObject json = new JSONObject() + json.put('results', XML.toJSONObject(xml)) + render contentType: 'application/json', text: json + } else { render toJson([results: [:]]) } + } + + private Map multiValuedParameters() { + Map multiValuedParams = [:] + if (params.derivedPartStatusId) { multiValuedParams.derivedPartStatusId = params.list('derivedPartStatusId') } + if (params.categoryId) { multiValuedParams.categoryId = params.list('categoryId') } + multiValuedParams + } +} Index: grails-app/controllers/com/lemans/testing/StuffController.groovy =================================================================== diff -u --- grails-app/controllers/com/lemans/testing/StuffController.groovy (revision 0) +++ grails-app/controllers/com/lemans/testing/StuffController.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,25 @@ +package com.lemans.testing + +import grails.converters.JSON + +@SuppressWarnings('Println') +class StuffController { + + def index() { + String wtf = params.wtf + if (wtf) { throw new IllegalArgumentException(wtf) } + + def artefactHandlers = grailsApplication.artefactHandlers*.class.simpleName + log.warn "*** artefactHandlers = $artefactHandlers" + + log.warn "*** interceptor artefact info = ${grailsApplication.getArtefactInfo('Interceptor').dump()}" + + def stuff = grailsApplication.allClasses.collect { + 'Artefact:' + (it.toString() - 'class') + }.sort() + + stuff += artefactHandlers.collect { 'ArtefactHandler: ' + it } + + render (stuff as JSON) + } +} Index: grails-app/domain/com/lemans/ds/attribute/AttributeName.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/attribute/AttributeName.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/attribute/AttributeName.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,28 @@ +package com.lemans.ds.attribute + +import com.lemans.services.Auditable + +class AttributeName extends Auditable { + Integer id + String attributeName + String attributeDisplayName + + static constraints = { + attributeName maxSize: 100, blank: false + attributeDisplayName maxSize: 100, nullable: true + } + + static mapping = { + table 'attributeName' + id column: 'attributeNameId' + } + + //implemented when PartAttribute is functional + /*boolean hasCategoryOrPartRelation() { + boolean categoryCount = Part.createCriteria().count { + // ('category', id) + isNull("dateDeleted") + } + }*/ + +} Index: grails-app/domain/com/lemans/ds/attribute/AttributeNameLocale.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/attribute/AttributeNameLocale.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/attribute/AttributeNameLocale.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,25 @@ +package com.lemans.ds.attribute + +import com.lemans.ds.DSLocale + +/** + * Created by MUmachi on 9/22/2017. + */ +class AttributeNameLocale extends DSLocale { + Integer id + Integer attributeNameId + String attributeName + String attributeDisplayName + + static constraints = { + attributeName nullable: true, maxSize: 100 + attributeDisplayName nullable: true, maxSize: 100 + } + + static mapping = { + table 'attributeNameLocale' + id column: 'attributeNameLocaleId' + attributeNameId updateable: false + } + +} Index: grails-app/domain/com/lemans/ds/attribute/AttributeValue.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/attribute/AttributeValue.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/attribute/AttributeValue.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,18 @@ +package com.lemans.ds.attribute + +import com.lemans.services.Auditable + +class AttributeValue extends Auditable { + Integer id + String attributeValue + + static constraints = { + attributeValue maxSize: 750, blank: false + + } + + static mapping = { + table 'attributeValue' + id column: 'attributeValueId' + } +} Index: grails-app/domain/com/lemans/ds/attribute/AttributeValueLocale.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/attribute/AttributeValueLocale.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/attribute/AttributeValueLocale.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,26 @@ +package com.lemans.ds.attribute + +import com.lemans.ds.DSLocale + +/** + * Created by MUmachi on 9/22/2017. + */ +class AttributeValueLocale extends DSLocale { + Integer id + String attributeValue + Integer attributeValueId + //String locale + + static constraints = { + //locale maxSize: 2 + attributeValue nullable: true, maxSize: 750 + + } + + static mapping = { + table 'attributeValueLocale' + id column: 'attributeValueLocaleId' + attributeValueId updateable: false + } + +} Index: grails-app/domain/com/lemans/ds/category/Category.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/category/Category.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/category/Category.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,50 @@ +package com.lemans.ds.category + +import com.lemans.services.Auditable + + +class Category extends Auditable { + + static final Integer MAX_DIRECT_CHILDREN = 999999 + + static final Integer MAX_DEPTH = 4 + + private static final Map IMMUTABLE_PROPERTIES = [ + catalogInstanceId: 'Catalog', + parentCategoryId: 'Category Parent', + sequence: 'Category Sequence' + ].asImmutable() + + Integer id + + String categoryName + + Integer catalogInstanceId + + Integer parentCategoryId + + Integer primaryMediaId + + String description + + Integer sequence + + static constraints = { + categoryName blank: false, maxSize: 100 + description nullable: true, maxSize: 2000 + parentCategoryId nullable: true + primaryMediaId nullable: true + sequence nullable: true + } + + static mapping = { + table 'Category' + id column: 'categoryId' + catalogInstanceId updateable: false + } + + @Override + protected Map immutables() { + Category.IMMUTABLE_PROPERTIES + } +} Index: grails-app/domain/com/lemans/ds/category/CategoryAttribute.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/category/CategoryAttribute.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/category/CategoryAttribute.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,36 @@ +package com.lemans.ds.category + +import com.lemans.services.Auditable + +class CategoryAttribute extends Auditable { + + private static final Map IMMUTABLE_PROPERTIES = [ + categoryId: 'Category', + attributeNameId: 'Attribute Name' + ].asImmutable() + + Integer id + Integer categoryId + Integer attributeNameId + Boolean isRequired + Boolean isDropdown + Boolean isHidden + Boolean isGroup + Boolean isKeyAttribute + Boolean allowMultipleValues + + static constraints = { + + } + + static mapping = { + table 'CategoryAttribute' + id column: 'categoryAttributeId' + } + + + @Override + protected Map immutables() { + CategoryAttribute.IMMUTABLE_PROPERTIES + } +} Index: grails-app/domain/com/lemans/ds/category/CategoryAttributeLocale.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/category/CategoryAttributeLocale.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/category/CategoryAttributeLocale.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,25 @@ +package com.lemans.ds.category + +import com.lemans.ds.DSLocale + +/** + * Created by MUmachi on 10/30/2017. + */ +class CategoryAttributeLocale extends DSLocale { + + Integer id + Integer categoryAttributeId + String attributeName + String attributeDisplayName + + static constraints = { + attributeName nullable: true, maxSize: 100 + attributeDisplayName nullable: true, maxSize: 100 + } + + static mapping = { + table 'CategoryAttributeLocale' + id column: 'categoryAttributeLocaleId' + categoryAttributeId updateable: false + } +} Index: grails-app/domain/com/lemans/ds/category/CategoryAttributeValue.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/category/CategoryAttributeValue.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/category/CategoryAttributeValue.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,30 @@ +package com.lemans.ds.category + +import com.lemans.services.Auditable + +class CategoryAttributeValue extends Auditable { + + private static final Map IMMUTABLE_PROPERTIES = [ + categoryAttributeId: 'Category Attribute', + attributeValueId: 'Attribute Value' + ].asImmutable() + + Integer id + Integer categoryAttributeId + Integer attributeValueId + + static constraints = { + + } + + static mapping = { + table 'CategoryAttributeValue' + id column: 'categoryAttributeValueId' + } + + + @Override + protected Map immutables() { + CategoryAttributeValue.IMMUTABLE_PROPERTIES + } +} Index: grails-app/domain/com/lemans/ds/category/CategoryAttributeValueLocale.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/category/CategoryAttributeValueLocale.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/category/CategoryAttributeValueLocale.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,23 @@ +package com.lemans.ds.category + +import com.lemans.ds.DSLocale + +/** + * Created by MUmachi on 10/30/2017. + */ +class CategoryAttributeValueLocale extends DSLocale { + + Integer id + Integer categoryAttributeValueId + String attributeValue + + static constraints = { + attributeValue maxSize: 750 + } + + static mapping = { + table 'CategoryAttributeValueLocale' + id column: 'categoryAttributeValueLocaleId' + categoryAttributeValueId updateable: false + } +} Index: grails-app/domain/com/lemans/ds/category/CategoryLocale.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/category/CategoryLocale.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/category/CategoryLocale.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,25 @@ +package com.lemans.ds.category + +import com.lemans.services.Auditable + +class CategoryLocale extends Auditable { + + Integer id + Integer categoryId + String locale + String categoryName + String description + + static constraints = { + categoryId nullable: false + locale nullable: false + categoryName nullable: true + description nullable: true + } + + static mapping = { + table 'CategoryLocale' + id column: 'categoryLocaleId' + categoryId updateable: false + } +} Index: grails-app/domain/com/lemans/ds/category/CategorySubComCode.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/category/CategorySubComCode.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/category/CategorySubComCode.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,24 @@ +package com.lemans.ds.category + +import com.lemans.services.Auditable +import groovy.transform.ToString + +@ToString +class CategorySubComCode extends Auditable { + + Integer id + + Integer categoryId + + Integer subComCodeId + + static constraints = { + } + + static mapping = { + table 'CategorySubComCode' + id column: 'categorySubComCodeId' + categoryId updateable: false + subComCodeId updateable: false + } +} Index: grails-app/domain/com/lemans/ds/explosion/ExplosionDiagram.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/explosion/ExplosionDiagram.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/explosion/ExplosionDiagram.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,22 @@ +package com.lemans.ds.explosion + +import com.lemans.services.Auditable + +class ExplosionDiagram extends Auditable { + + Integer id + String title + String jsonData + Integer isActive + + static constraints = { + title nullable: false + jsonData nullable: false + isActive nullable: false + } + + static mapping = { + table 'ExplosionDiagram' + id column: 'explosionDiagramId' + } +} Index: grails-app/domain/com/lemans/ds/fitment/Make.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/fitment/Make.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/fitment/Make.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,28 @@ +package com.lemans.ds.fitment + +import com.lemans.services.Auditable +import groovy.transform.ToString + +@ToString +class Make extends Auditable { + + Integer id + + String makeName + + String modelNameFormat + + static constraints = { + makeName maxSize: 100, blank: false + modelNameFormat maxSize: 50, blank: false + } + + static hasMany = [models: Model] + + static transients = ['models'] + + static mapping = { + table 'Make' + id column: 'makeId' + } +} Index: grails-app/domain/com/lemans/ds/fitment/Model.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/fitment/Model.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/fitment/Model.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,27 @@ +package com.lemans.ds.fitment + +import com.lemans.services.Auditable + +class Model extends Auditable { + + Integer id + Make make + String modelName + String modelNameFormat + Integer vehicleTypeId + + static constraints = { + modelName maxSize: 400, blank: false + modelNameFormat nullable: true, blank: false, maxSize: 50 + } + + static hasMany = [modelYears: ModelYear, modelSegments: ModelSegment] + + static transients = ['modelName'] + + static mapping = { + table 'Model' + id column: 'modelId' + make column: 'makeId', updateable: false + } +} Index: grails-app/domain/com/lemans/ds/fitment/ModelSegment.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/fitment/ModelSegment.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/fitment/ModelSegment.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,27 @@ +package com.lemans.ds.fitment + +import com.lemans.services.Auditable + +/** + * Created by vramisetti on 4/10/2017. + */ +class ModelSegment extends Auditable { + + Integer id + Segment segment + String segmentValue + + static constraints = { + segmentValue nullable: true, maxSize: 100 + } + + static belongsTo = [model: Model] + + static mapping = { + table 'ModelSegment' + id column: 'modelSegmentId' + model column: 'modelId' + segment column: 'segmentId' + segmentValue column: 'value' + } +} Index: grails-app/domain/com/lemans/ds/fitment/ModelYear.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/fitment/ModelYear.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/fitment/ModelYear.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,33 @@ +package com.lemans.ds.fitment + +import com.lemans.services.Auditable +import groovy.transform.ToString + +import java.time.Year + +@ToString +class ModelYear extends Auditable { + + Integer id + Model model + @SuppressWarnings(['GrailsDomainReservedSqlKeywordName'])//TODO: remove this if possible + Integer year + + static constraints = { + year blank: false, validator: { + Integer currentYear = Year.now().value + if ((it < 1894) || (it > currentYear + 1)) { return ['modelYear.year.rangeExceeded', currentYear + 1] } + } + } + + static belongsTo = [model: Model] + + static hasMany = [partFitments: PartFitment] + + static mapping = { + table 'ModelYear' + id column: 'modelYearId' + year column: 'year' + model column: 'modelId'//, updateable: false + } +} Index: grails-app/domain/com/lemans/ds/fitment/PartFitment.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/fitment/PartFitment.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/fitment/PartFitment.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,26 @@ +package com.lemans.ds.fitment + +import com.lemans.ds.part.Part +import com.lemans.services.Auditable + +/** + * Created by VRAMISETTI on 4/15/2017. + */ +class PartFitment extends Auditable { + + Integer id + String note + + static constraints = { + note maxSize: 200, nullable: true + } + + static belongsTo = [partNumber: Part, modelYearId: ModelYear] + + static mapping = { + table 'PartFitment' + id column: 'partFitmentId' + partNumber column: 'partNumber' + modelYearId column: 'modelYearId' + } +} Index: grails-app/domain/com/lemans/ds/fitment/Segment.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/fitment/Segment.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/fitment/Segment.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,29 @@ +package com.lemans.ds.fitment + +import com.lemans.services.Auditable + +/** + * Created by vramisetti on 4/10/2017. + */ +class Segment extends Auditable { + + Integer id + String segmentCode + String segmentName + Integer sequence + + static constraints = { + segmentCode maxSize: 10, blank: false + segmentName maxSize: 50, blank: false + sequence nullable: true + } + + static mapping = { + table 'Segment' + id column: 'segmentId' + } + + static List segmentCodes() { + this.executeQuery('SELECT segmentCode FROM Segment WHERE dateDeleted IS NULL') + } +} Index: grails-app/domain/com/lemans/ds/flag/Flag.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/flag/Flag.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/flag/Flag.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,34 @@ +package com.lemans.ds.flag + +import com.lemans.services.Auditable + +class Flag extends Auditable { + + private static final Map IMMUTABLE_PROPERTIES = [ + flagName: 'Flag Name' + ].asImmutable() + + Integer id + String entityClass + String flagName + String flagDisplayName + String flagTypeId + + static constraints = { + entityClass maxSize: 50, blank: false + flagName maxSize: 100, blank: false + flagDisplayName nullable: true, maxSize: 250 + } + + static mapping = { + table 'Flag' + id column: 'flagId' + flagName updateable: false + flagTypeId updateable: false + } + + @Override + protected Map immutables() { + Flag.IMMUTABLE_PROPERTIES + } +} Index: grails-app/domain/com/lemans/ds/flag/FlagValue.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/flag/FlagValue.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/flag/FlagValue.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,23 @@ +package com.lemans.ds.flag + +import com.lemans.services.Auditable +import groovy.transform.ToString + +@ToString +class FlagValue extends Auditable { + + Integer id + Integer flagId + String entityId + + static constraints = { + entityId maxSize: 50, blank: false + } + + static mapping = { + table 'flagValue' + id column: 'flagValueId' + flagId updateable: false + entityId updateable: false + } +} Index: grails-app/domain/com/lemans/ds/importing/LocaleImportProcess.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/importing/LocaleImportProcess.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/importing/LocaleImportProcess.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,47 @@ +package com.lemans.ds.importing + +import com.lemans.services.Auditable +import groovy.transform.ToString + +@ToString(includeNames = true) +class LocaleImportProcess extends Auditable { + + Integer id + + Integer mimeTypeId + + String extension + + String importType + + String originalFileName + + String status + + String message + + Date startDate + + Date endDate + + static constraints = { + + originalFileName maxSize: 255 + importType maxSize: 80 + mimeTypeId nullable: true + message nullable: true, maxSize: 255 + extension nullable: true, maxSize: 7 + status maxSize: 50 + startDate nullable: true + endDate nullable: true + } + + + static mapping = { + table 'LocaleImportProcess' + id column: 'localeImportProcessId' + originalFileName updateable: false + importType updateable: false + } + +} Index: grails-app/domain/com/lemans/ds/part/Part.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/part/Part.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/part/Part.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,33 @@ +package com.lemans.ds.part + +import com.lemans.ds.fitment.PartFitment +import com.lemans.services.Auditable + + +/** + * Represents additional data for Parts that is used by DS. Actual Parts may only be created or deleted by mainframe. + */ +class Part extends Auditable { + + static final int UNASSIGNED_CATEGORY_ID = -1 + static final String UNASSIGNED_PRODUCT_ID = '-1' + + String id + + String marketingDescr + Integer categoryId + Integer primaryMediaId + + static constraints = { + marketingDescr nullable: true, maxSize: 100 + categoryId nullable: true + primaryMediaId nullable: true + } + + static hasMany = [partFitments: PartFitment] + + static mapping = { + table 'Part' + id column: 'partNumber' + } +} Index: grails-app/domain/com/lemans/ds/part/PartAssociation.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/part/PartAssociation.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/part/PartAssociation.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,22 @@ +package com.lemans.ds.part + +import com.lemans.services.Auditable + +class PartAssociation extends Auditable { + + Integer id + String partNumber + String relatedPartNumber + Integer associationTypeId + + static constraints = { + partNumber nullable: false + relatedPartNumber nullable: false + associationTypeId nullable: false + } + + static mapping = { + table 'PartAssociation' + id column: 'partAssociationId' + } +} Index: grails-app/domain/com/lemans/ds/part/PartLocale.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/part/PartLocale.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/part/PartLocale.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,27 @@ +package com.lemans.ds.part + +import com.lemans.ds.DSLocale + +class PartLocale extends DSLocale { + + Integer id + String partNumber + String partDescr + String marketingDescr + String specialInstructions + String partSpecificText + + static constraints = { + partNumber nullable: false + partDescr nullable: true, maxSize: 50 + marketingDescr nullable: true, maxSize: 100 + specialInstructions nullable: true, maxSize: 400 + partSpecificText nullable: true + } + + static mapping = { + table 'PartLocale' + id column: 'partLocaleId' + partNumber updateable: false + } +} Index: grails-app/domain/com/lemans/ds/part/partmetadata/PartMetadata.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/part/partmetadata/PartMetadata.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/part/partmetadata/PartMetadata.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,36 @@ +package com.lemans.ds.part.partmetadata + +import com.lemans.services.Auditable +import groovy.transform.ToString + + +/** + * Represents additional metadata for Parts that is used by DS. Actual Parts may only be created or deleted by mainframe. + */ +@ToString +class PartMetadata extends Auditable { + + String id + String partSpecificText + String internalNotes + String relatedParts + String relatedProducts + String certificationUS + String certificationEU + String oemPartNumber + + static constraints = { + partSpecificText nullable: true + internalNotes nullable: true + relatedParts nullable: true + relatedProducts nullable: true + certificationUS nullable: true + certificationEU nullable: true + oemPartNumber nullable: true + } + + static mapping = { + table 'PartMetadata' + id column: 'partNumber', generator: 'assigned' + } +} Index: grails-app/domain/com/lemans/ds/product/Product.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/product/Product.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/product/Product.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,67 @@ +package com.lemans.ds.product + +import com.lemans.services.Auditable +import groovy.transform.ToString + + +@ToString +class Product extends Auditable { + + static final Integer VIRTUAL_CATALOG_ID = 0 + + private static final Map IMMUTABLE_PROPERTIES = [ + catalogInstanceId: 'Catalog' + ].asImmutable() + + Integer id + + Integer catalogInstanceId + + Integer categoryId + + Integer brandId + + String productName + + Boolean isDigiActive + + Integer primaryMediaId + + String description + + String caption + + Date effectiveDate + + static constraints = { + categoryId nullable: true, validator: { value, product -> + if (value == null && product.catalogInstanceId == VIRTUAL_CATALOG_ID) { + return 'nullable' + } + } + + brandId nullable: true, validator: { value, product -> + if (value == null && product.catalogInstanceId == VIRTUAL_CATALOG_ID) { + return 'nullable' + } + } + + productName nullable: false, minSize: 3, maxSize: 150 + description nullable: true, maxSize: 2000 + caption nullable: true, maxSize: 150 + effectiveDate nullable: true + primaryMediaId nullable: true + isDigiActive nullable: false + } + + static mapping = { + table 'Product' + id column: 'productId' + catalogInstanceId updateable: false + } + + @Override + protected Map immutables() { + Product.IMMUTABLE_PROPERTIES + } +} Index: grails-app/domain/com/lemans/ds/product/ProductAssociation.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/product/ProductAssociation.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/product/ProductAssociation.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,22 @@ +package com.lemans.ds.product + +import com.lemans.services.Auditable + +class ProductAssociation extends Auditable { + + Integer id + String productId + String relatedProductId + Integer associationTypeId + + static constraints = { + productId nullable: false + relatedProductId nullable: false + associationTypeId nullable: false + } + + static mapping = { + table 'ProductAssociation' + id column: 'productAssociationId' + } +} Index: grails-app/domain/com/lemans/ds/product/ProductCategoryAttribute.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/product/ProductCategoryAttribute.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/product/ProductCategoryAttribute.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,43 @@ +package com.lemans.ds.product + +import com.lemans.services.Auditable +import groovy.transform.ToString + +/** + * Created by MUmachi on 10/24/2017. + */ +@ToString +class ProductCategoryAttribute extends Auditable { + + + Integer id + + Integer productId + + Integer categoryAttributeId + + Boolean isDropdown + + Boolean isHidden + + Boolean isSplit + + Boolean isGroup + + Boolean isKeyAttribute + + static constraints = { + isDropdown nullable: true + isHidden nullable: true + isGroup nullable: true + isKeyAttribute nullable: true + } + + static mapping = { + table 'ProductCategoryAttribute' + id column: 'productCategoryAttributeId' + categoryAttributeId updateable: false + } + + +} Index: grails-app/domain/com/lemans/ds/product/ProductFeature.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/product/ProductFeature.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/product/ProductFeature.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,26 @@ +package com.lemans.ds.product + +import com.lemans.services.Auditable +import groovy.transform.ToString + +@ToString(includeNames = true) +class ProductFeature extends Auditable { + + Integer id + + Integer productId + + Integer featureTypeId + + String featureText + + static constraints = { + featureText blank: false, maxSize: 1500 + } + + static mapping = { + table 'ProductFeature' + id column: 'productFeatureId' + productId updateable: false + } +} Index: grails-app/domain/com/lemans/ds/product/ProductFeatureLocale.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/product/ProductFeatureLocale.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/product/ProductFeatureLocale.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,24 @@ +package com.lemans.ds.product + +import com.lemans.ds.DSLocale + +class ProductFeatureLocale extends DSLocale { + + Integer id + + Integer productFeatureId + + String featureText + + + + static constraints = { + featureText nullable: true, minSize: 3, maxSize: 1500 + } + + static mapping = { + table 'ProductFeatureLocale' + id column: 'productFeatureLocaleId' + productFeatureId updateable: false + } +} Index: grails-app/domain/com/lemans/ds/product/ProductLocale.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/product/ProductLocale.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/product/ProductLocale.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,34 @@ +package com.lemans.ds.product + +import com.lemans.ds.DSLocale + +/** + * Created by MUmachi on 9/21/2017. + */ +class ProductLocale extends DSLocale { + + Integer id + + Integer productId + + String productName + + String caption + + String description + + + + static constraints = { + productName nullable: true, minSize: 3, maxSize: 150 + description nullable: true, maxSize: 2000 + caption nullable: true, maxSize: 150 + } + + static mapping = { + table 'ProductLocale' + id column: 'productLocaleId' + productId updateable: false + } + +} Index: grails-app/domain/com/lemans/ds/publicationcategory/ProductPublicationCategory.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/publicationcategory/ProductPublicationCategory.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/publicationcategory/ProductPublicationCategory.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,20 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.services.Auditable + +class ProductPublicationCategory extends Auditable { + + Integer id + Integer productId + Integer categoryId + + static constraints = { + productId nullable: false + categoryId nullable: false + } + + static mapping = { + table 'ProductPublicationCategory' + id column: 'productPublicationCategoryId' + } +} Index: grails-app/domain/com/lemans/ds/publicationcategory/PublicationCategory.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/publicationcategory/PublicationCategory.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/publicationcategory/PublicationCategory.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,30 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.services.Auditable + +class PublicationCategory extends Auditable { + + static final Integer MAX_DIRECT_CHILDREN = 999999 + + Integer id + String categoryName + String description + Integer parentCategoryId + Integer primaryMediaId + Integer sequence + Date effectiveDate + + static constraints = { + categoryName nullable: false + description nullable: true + parentCategoryId nullable: true + primaryMediaId nullable: true + sequence nullable: false + effectiveDate nullable: true + } + + static mapping = { + table 'PublicationCategory' + id column: 'categoryId', updateable: false, insertable: false + } +} Index: grails-app/domain/com/lemans/ds/publicationcategory/PublicationCategoryAttribute.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/publicationcategory/PublicationCategoryAttribute.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/publicationcategory/PublicationCategoryAttribute.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,28 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.services.Auditable + +class PublicationCategoryAttribute extends Auditable { + + Integer id + Integer publicationCategoryId + Integer attributeNameId + Integer sequence + Integer source + Integer sourceId + + static constraints = { + publicationCategoryId nullable: false + attributeNameId nullable: false + sequence nullable: true + source nullable: true + sourceId nullable: true + } + + static mapping = { + table 'PublicationCategoryAttribute' + id column: 'publicationCategoryAttributeId' + publicationCategoryId updateable: false + attributeNameId updateable: false + } +} Index: grails-app/domain/com/lemans/ds/splitvalidation/JobStatus.groovy =================================================================== diff -u --- grails-app/domain/com/lemans/ds/splitvalidation/JobStatus.groovy (revision 0) +++ grails-app/domain/com/lemans/ds/splitvalidation/JobStatus.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,20 @@ +package com.lemans.ds.splitvalidation + +import com.lemans.services.Auditable + +class JobStatus extends Auditable { + + Integer id + Date startDate + Date endDate + + static constraints = { + startDate nullable: false + endDate nullable: true + } + + static mapping = { + table 'JobStatus' + id column: 'jobStatusId' + } +} Index: grails-app/i18n/messages.properties =================================================================== diff -u --- grails-app/i18n/messages.properties (revision 0) +++ grails-app/i18n/messages.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,112 @@ +default.doesnt.match.message={0} with value [{2}] does not match the required pattern [{3}] +default.invalid.url.message={0} with value [{2}] is not a valid URL +default.invalid.creditCard.message={0} with value [{2}] is not a valid credit card number +default.invalid.email.message={0} with value [{2}] is not a valid e-mail address +default.invalid.range.message={0} with value [{2}] does not fall within the valid range from [{3}] to [{4}] +default.invalid.size.message={0} with value [{2}] does not fall within the valid size range from [{3}] to [{4}] +default.invalid.max.message={0} with value [{2}] exceeds maximum value [{3}] +default.invalid.min.message={0} with value [{2}] is less than minimum value [{3}] +default.invalid.max.size.message={0} with value [{2}] exceeds the maximum size of [{3}] +default.invalid.min.size.message={0} with value [{2}] is less than the minimum size of [{3}] +default.invalid.validator.message={0} with value [{2}] does not pass custom validation +default.not.inlist.message={0} with value [{2}] is not contained within the list [{3}] +default.blank.message={0} is required +default.not.equal.message={0} with value [{2}] cannot equal [{3}] +default.null.message={0} is required +default.not.unique.message={0} with value [{2}] must be unique + +default.paginate.prev=Previous +default.paginate.next=Next +default.boolean.true=True +default.boolean.false=False +default.date.format=yyyy-MM-dd HH:mm:ss z +default.number.format=0 + +default.created.message={0} {1} created +default.updated.message={0} {1} updated +default.deleted.message={0} {1} deleted +default.not.deleted.message={0} {1} could not be deleted +default.not.found.message={0} not found with id {1} +default.optimistic.locking.failure=Another user has updated this {0} while you were editing + +default.home.label=Home +default.list.label={0} List +default.add.label=Add {0} +default.new.label=New {0} +default.create.label=Create {0} +default.show.label=Show {0} +default.edit.label=Edit {0} + +default.button.create.label=Create +default.button.edit.label=Edit +default.button.update.label=Update +default.button.delete.label=Delete +default.button.delete.confirm.message=Are you sure? + +# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author) +typeMismatch.java.net.URL={0} must be a valid URL +typeMismatch.java.net.URI={0} must be a valid URI +typeMismatch.java.util.Date={0} must be a valid Date +typeMismatch.java.lang.Double={0} must be a valid number +typeMismatch.java.lang.Integer={0} must be a valid number +typeMismatch.java.lang.Long={0} must be a valid number +typeMismatch.java.lang.Short={0} must be a valid number +typeMismatch.java.math.BigDecimal={0} must be a valid number +typeMismatch.java.math.BigInteger={0} must be a valid number +typeMismatch={0} is type-mismatched + +#categoryAttribute +categoryAttribute.allowMultipleValues.invalid=Cannot alter flag as parts assigned to category has multiple values assigned to this attribute. +categoryAttribute.partsWithAttributeName.exists=Cannot delete Category Attribute that has parts with the same attribute Name. + +#Lemans +immutable={0} may not be changed +undeletable={0} may not be deleted + +#Category +category.catalogInstanceId.label=Catalog +category.categoryName.label=Category Name + + +#Product +product.productName.label=Product Name +product.catalogInstanceId.label=Catalog +product.brandId.label=Brand +product.categoryId.label=Category + +#Product Feature +productFeature.featureText.label=Product Feature Text +productFeature.featureTypeId.label=Product Feature Type + +#Make +make.parts.assigned=Cannot delete Make that has {0} parts associated +make.models.assigned=Cannot delete Make that has {0} models associated + +#Model +model.make.nullable=Make is invalid or not provided +model.modelName.blank=At least one Segment is required for a Model +model.yearRangeExceeded=Found Invalid Years {0}. Years should be in between 1894 and {1} +model.duplicate=Model with following segments and segment Values already exists +model.emptySegments=At least One Segment is required for a Model + +#Model Year +modelYear.year.rangeExceeded=Year should be in between 1894 and {0} + + +makeModel.modelNameFormat.duplicateSegmentCode=Invalid Model Name Format. Segments can not be repeated. +makeModel.modelNameFormat.invalidSegmentCode=Invalid Model Name Format. Invalid segment codes present. + +modelSegment.segmentValue.label=Value + +localeImportProcess.id.stillProcessing=Process can't be deleted, until it is completely processed. + +partAssociation.duplicate=PartAssociation for partNumber {0} with associationTypeId {1, number, #} and relatedPartNumber {2} exists. +productAssociation.duplicate=productAssociation for productId {0} with associationTypeId {1, number, #} and relatedProductId {2} exists. + +publicationCategory.notDeletable=Publication Category may not be deleted +publicationCategory.cyclic=PublicationCategory may not be moved to create a cycle +productPublicationCategory.duplicate=Product{0, number, #} has already been assigned. + +publicationCategory.invalid=Invalid Publication Category + +publicationCategoryAttribute.duplicate=Duplicate publicationCategoryAttribute \ No newline at end of file Index: grails-app/i18n/messages_cs_CZ.properties =================================================================== diff -u --- grails-app/i18n/messages_cs_CZ.properties (revision 0) +++ grails-app/i18n/messages_cs_CZ.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,55 @@ +default.doesnt.match.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] neodpovídá požadovanému vzoru [{3}] +default.invalid.url.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není validní URL +default.invalid.creditCard.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není validní číslo kreditní karty +default.invalid.email.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není validní emailová adresa +default.invalid.range.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není v povoleném rozmezí od [{3}] do [{4}] +default.invalid.size.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není v povoleném rozmezí od [{3}] do [{4}] +default.invalid.max.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] překračuje maximální povolenou hodnotu [{3}] +default.invalid.min.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] je menší než minimální povolená hodnota [{3}] +default.invalid.max.size.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] překračuje maximální velikost [{3}] +default.invalid.min.size.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] je menší než minimální velikost [{3}] +default.invalid.validator.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] neprošla validací +default.not.inlist.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není obsažena v seznamu [{3}] +default.blank.message=Položka [{0}] třídy [{1}] nemůže být prázdná +default.not.equal.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] nemůže být stejná jako [{3}] +default.null.message=Položka [{0}] třídy [{1}] nemůže být prázdná +default.not.unique.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] musí být unikátní + +default.paginate.prev=Předcházející +default.paginate.next=Následující +default.boolean.true=Pravda +default.boolean.false=Nepravda +default.date.format=dd. MM. yyyy HH:mm:ss z +default.number.format=0 + +default.created.message={0} {1} vytvořeno +default.updated.message={0} {1} aktualizováno +default.deleted.message={0} {1} smazáno +default.not.deleted.message={0} {1} nelze smazat +default.not.found.message={0} nenalezen s id {1} +default.optimistic.locking.failure=Jiný uživatel aktualizoval záznam {0}, právě když byl vámi editován + +default.home.label=Domů +default.list.label={0} Seznam +default.add.label=Přidat {0} +default.new.label=Nový {0} +default.create.label=Vytvořit {0} +default.show.label=Ukázat {0} +default.edit.label=Editovat {0} + +default.button.create.label=Vytvoř +default.button.edit.label=Edituj +default.button.update.label=Aktualizuj +default.button.delete.label=Smaž +default.button.delete.confirm.message=Jste si jistý? + +# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author) +typeMismatch.java.net.URL=Položka {0} musí být validní URL +typeMismatch.java.net.URI=Položka {0} musí být validní URI +typeMismatch.java.util.Date=Položka {0} musí být validní datum +typeMismatch.java.lang.Double=Položka {0} musí být validní desetinné číslo +typeMismatch.java.lang.Integer=Položka {0} musí být validní číslo +typeMismatch.java.lang.Long=Položka {0} musí být validní číslo +typeMismatch.java.lang.Short=Položka {0} musí být validní číslo +typeMismatch.java.math.BigDecimal=Položka {0} musí být validní číslo +typeMismatch.java.math.BigInteger=Položka {0} musí být validní číslo Index: grails-app/i18n/messages_da.properties =================================================================== diff -u --- grails-app/i18n/messages_da.properties (revision 0) +++ grails-app/i18n/messages_da.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,56 @@ +default.doesnt.match.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overholder ikke mønsteret [{3}] +default.invalid.url.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke en gyldig URL +default.invalid.creditCard.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke et gyldigt kreditkortnummer +default.invalid.email.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke en gyldig e-mail adresse +default.invalid.range.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] ligger ikke inden for intervallet fra [{3}] til [{4}] +default.invalid.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] ligger ikke inden for størrelsen fra [{3}] til [{4}] +default.invalid.max.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overstiger den maksimale værdi [{3}] +default.invalid.min.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er under den minimale værdi [{3}] +default.invalid.max.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overstiger den maksimale størrelse på [{3}] +default.invalid.min.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er under den minimale størrelse på [{3}] +default.invalid.validator.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overholder ikke den brugerdefinerede validering +default.not.inlist.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] findes ikke i listen [{3}] +default.blank.message=Feltet [{0}] i klassen [{1}] kan ikke være tom +default.not.equal.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] må ikke være [{3}] +default.null.message=Feltet [{0}] i klassen [{1}] kan ikke være null +default.not.unique.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] skal være unik + +default.paginate.prev=Forrige +default.paginate.next=Næste +default.boolean.true=Sand +default.boolean.false=Falsk +default.date.format=yyyy-MM-dd HH:mm:ss z +default.number.format=0 + +default.created.message={0} {1} oprettet +default.updated.message={0} {1} opdateret +default.deleted.message={0} {1} slettet +default.not.deleted.message={0} {1} kunne ikke slettes +default.not.found.message={0} med id {1} er ikke fundet +default.optimistic.locking.failure=En anden bruger har opdateret denne {0} imens du har lavet rettelser + +default.home.label=Hjem +default.list.label={0} Liste +default.add.label=Tilføj {0} +default.new.label=Ny {0} +default.create.label=Opret {0} +default.show.label=Vis {0} +default.edit.label=Ret {0} + +default.button.create.label=Opret +default.button.edit.label=Ret +default.button.update.label=Opdater +default.button.delete.label=Slet +default.button.delete.confirm.message=Er du sikker? + +# Databindingsfejl. Brug "typeMismatch.$className.$propertyName for at passe til en given klasse (f.eks typeMismatch.Book.author) +typeMismatch.java.net.URL=Feltet {0} skal være en valid URL +typeMismatch.java.net.URI=Feltet {0} skal være en valid URI +typeMismatch.java.util.Date=Feltet {0} skal være en valid Dato +typeMismatch.java.lang.Double=Feltet {0} skal være et valid tal +typeMismatch.java.lang.Integer=Feltet {0} skal være et valid tal +typeMismatch.java.lang.Long=Feltet {0} skal være et valid tal +typeMismatch.java.lang.Short=Feltet {0} skal være et valid tal +typeMismatch.java.math.BigDecimal=Feltet {0} skal være et valid tal +typeMismatch.java.math.BigInteger=Feltet {0} skal være et valid tal + Index: grails-app/i18n/messages_de.properties =================================================================== diff -u --- grails-app/i18n/messages_de.properties (revision 0) +++ grails-app/i18n/messages_de.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,55 @@ +default.doesnt.match.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] entspricht nicht dem vorgegebenen Muster [{3}] +default.invalid.url.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige URL +default.invalid.creditCard.message=Das Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige Kreditkartennummer +default.invalid.email.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige E-Mail Adresse +default.invalid.range.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht im Wertebereich von [{3}] bis [{4}] +default.invalid.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht im Wertebereich von [{3}] bis [{4}] +default.invalid.max.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist größer als der Höchstwert von [{3}] +default.invalid.min.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist kleiner als der Mindestwert von [{3}] +default.invalid.max.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] übersteigt den Höchstwert von [{3}] +default.invalid.min.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] unterschreitet den Mindestwert von [{3}] +default.invalid.validator.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist ungültig +default.not.inlist.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht in der Liste [{3}] enthalten. +default.blank.message=Die Eigenschaft [{0}] des Typs [{1}] darf nicht leer sein +default.not.equal.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] darf nicht gleich [{3}] sein +default.null.message=Die Eigenschaft [{0}] des Typs [{1}] darf nicht null sein +default.not.unique.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] darf nur einmal vorkommen + +default.paginate.prev=Vorherige +default.paginate.next=Nächste +default.boolean.true=Wahr +default.boolean.false=Falsch +default.date.format=dd.MM.yyyy HH:mm:ss z +default.number.format=0 + +default.created.message={0} {1} wurde angelegt +default.updated.message={0} {1} wurde geändert +default.deleted.message={0} {1} wurde gelöscht +default.not.deleted.message={0} {1} konnte nicht gelöscht werden +default.not.found.message={0} mit der id {1} wurde nicht gefunden +default.optimistic.locking.failure=Ein anderer Benutzer hat das {0} Object geändert während Sie es bearbeitet haben + +default.home.label=Home +default.list.label={0} Liste +default.add.label={0} hinzufügen +default.new.label={0} anlegen +default.create.label={0} anlegen +default.show.label={0} anzeigen +default.edit.label={0} bearbeiten + +default.button.create.label=Anlegen +default.button.edit.label=Bearbeiten +default.button.update.label=Aktualisieren +default.button.delete.label=Löschen +default.button.delete.confirm.message=Sind Sie sicher? + +# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author) +typeMismatch.java.net.URL=Die Eigenschaft {0} muss eine gültige URL sein +typeMismatch.java.net.URI=Die Eigenschaft {0} muss eine gültige URI sein +typeMismatch.java.util.Date=Die Eigenschaft {0} muss ein gültiges Datum sein +typeMismatch.java.lang.Double=Die Eigenschaft {0} muss eine gültige Zahl sein +typeMismatch.java.lang.Integer=Die Eigenschaft {0} muss eine gültige Zahl sein +typeMismatch.java.lang.Long=Die Eigenschaft {0} muss eine gültige Zahl sein +typeMismatch.java.lang.Short=Die Eigenschaft {0} muss eine gültige Zahl sein +typeMismatch.java.math.BigDecimal=Die Eigenschaft {0} muss eine gültige Zahl sein +typeMismatch.java.math.BigInteger=Die Eigenschaft {0} muss eine gültige Zahl sein Index: grails-app/i18n/messages_es.properties =================================================================== diff -u --- grails-app/i18n/messages_es.properties (revision 0) +++ grails-app/i18n/messages_es.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,55 @@ +default.doesnt.match.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no corresponde al patrón [{3}] +default.invalid.url.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es una URL válida +default.invalid.creditCard.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es un número de tarjeta de crédito válida +default.invalid.email.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es una dirección de correo electrónico válida +default.invalid.range.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no entra en el rango válido de [{3}] a [{4}] +default.invalid.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no entra en el tamaño válido de [{3}] a [{4}] +default.invalid.max.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] excede el valor máximo [{3}] +default.invalid.min.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] es menos que el valor mínimo [{3}] +default.invalid.max.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] excede el tamaño máximo de [{3}] +default.invalid.min.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] es menor que el tamaño mínimo de [{3}] +default.invalid.validator.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es válido +default.not.inlist.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no esta contenido dentro de la lista [{3}] +default.blank.message=La propiedad [{0}] de la clase [{1}] no puede ser vacía +default.not.equal.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no puede igualar a [{3}] +default.null.message=La propiedad [{0}] de la clase [{1}] no puede ser nulo +default.not.unique.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] debe ser única + +default.paginate.prev=Anterior +default.paginate.next=Siguiente +default.boolean.true=Verdadero +default.boolean.false=Falso +default.date.format=yyyy-MM-dd HH:mm:ss z +default.number.format=0 + +default.created.message={0} {1} creado +default.updated.message={0} {1} actualizado +default.deleted.message={0} {1} eliminado +default.not.deleted.message={0} {1} no puede eliminarse +default.not.found.message=No se encuentra {0} con id {1} +default.optimistic.locking.failure=Mientras usted editaba, otro usuario ha actualizado su {0} + +default.home.label=Principal +default.list.label={0} Lista +default.add.label=Agregar {0} +default.new.label=Nuevo {0} +default.create.label=Crear {0} +default.show.label=Mostrar {0} +default.edit.label=Editar {0} + +default.button.create.label=Crear +default.button.edit.label=Editar +default.button.update.label=Actualizar +default.button.delete.label=Eliminar +default.button.delete.confirm.message=¿Está usted seguro? + +# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author) +typeMismatch.java.net.URL=La propiedad {0} debe ser una URL válida +typeMismatch.java.net.URI=La propiedad {0} debe ser una URI válida +typeMismatch.java.util.Date=La propiedad {0} debe ser una fecha válida +typeMismatch.java.lang.Double=La propiedad {0} debe ser un número válido +typeMismatch.java.lang.Integer=La propiedad {0} debe ser un número válido +typeMismatch.java.lang.Long=La propiedad {0} debe ser un número válido +typeMismatch.java.lang.Short=La propiedad {0} debe ser un número válido +typeMismatch.java.math.BigDecimal=La propiedad {0} debe ser un número válido +typeMismatch.java.math.BigInteger=La propiedad {0} debe ser un número válido \ No newline at end of file Index: grails-app/i18n/messages_fr.properties =================================================================== diff -u --- grails-app/i18n/messages_fr.properties (revision 0) +++ grails-app/i18n/messages_fr.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,19 @@ +default.doesnt.match.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne correspond pas au pattern [{3}] +default.invalid.url.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas une URL valide +default.invalid.creditCard.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas un numéro de carte de crédit valide +default.invalid.email.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas une adresse e-mail valide +default.invalid.range.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas contenue dans l'intervalle [{3}] à [{4}] +default.invalid.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas contenue dans l'intervalle [{3}] à [{4}] +default.invalid.max.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est supérieure à la valeur maximum [{3}] +default.invalid.min.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est inférieure à la valeur minimum [{3}] +default.invalid.max.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est supérieure à la valeur maximum [{3}] +default.invalid.min.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est inférieure à la valeur minimum [{3}] +default.invalid.validator.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas valide +default.not.inlist.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne fait pas partie de la liste [{3}] +default.blank.message=La propriété [{0}] de la classe [{1}] ne peut pas être vide +default.not.equal.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne peut pas être égale à [{3}] +default.null.message=La propriété [{0}] de la classe [{1}] ne peut pas être nulle +default.not.unique.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] doit être unique + +default.paginate.prev=Précédent +default.paginate.next=Suivant Index: grails-app/i18n/messages_it.properties =================================================================== diff -u --- grails-app/i18n/messages_it.properties (revision 0) +++ grails-app/i18n/messages_it.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,55 @@ +default.doesnt.match.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non corrisponde al pattern [{3}] +default.invalid.url.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un URL valido +default.invalid.creditCard.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un numero di carta di credito valido +default.invalid.email.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un indirizzo email valido +default.invalid.range.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non rientra nell'intervallo valido da [{3}] a [{4}] +default.invalid.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non rientra nell'intervallo di dimensioni valide da [{3}] a [{4}] +default.invalid.max.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è maggiore di [{3}] +default.invalid.min.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è minore di [{3}] +default.invalid.max.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è maggiore di [{3}] +default.invalid.min.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è minore di [{3}] +default.invalid.validator.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è valida +default.not.inlist.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è contenuta nella lista [{3}] +default.blank.message=La proprietà [{0}] della classe [{1}] non può essere vuota +default.not.equal.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non può essere uguale a [{3}] +default.null.message=La proprietà [{0}] della classe [{1}] non può essere null +default.not.unique.message=La proprietà [{0}] della classe [{1}] con valore [{2}] deve essere unica + +default.paginate.prev=Precedente +default.paginate.next=Successivo +default.boolean.true=Vero +default.boolean.false=Falso +default.date.format=dd/MM/yyyy HH:mm:ss z +default.number.format=0 + +default.created.message={0} {1} creato +default.updated.message={0} {1} aggiornato +default.deleted.message={0} {1} eliminato +default.not.deleted.message={0} {1} non può essere eliminato +default.not.found.message={0} non trovato con id {1} +default.optimistic.locking.failure=Un altro utente ha aggiornato questo {0} mentre si era in modifica + +default.home.label=Home +default.list.label={0} Elenco +default.add.label=Aggiungi {0} +default.new.label=Nuovo {0} +default.create.label=Crea {0} +default.show.label=Mostra {0} +default.edit.label=Modifica {0} + +default.button.create.label=Crea +default.button.edit.label=Modifica +default.button.update.label=Aggiorna +default.button.delete.label=Elimina +default.button.delete.confirm.message=Si è sicuri? + +# Data binding errors. Usa "typeMismatch.$className.$propertyName per la personalizzazione (es typeMismatch.Book.author) +typeMismatch.java.net.URL=La proprietà {0} deve essere un URL valido +typeMismatch.java.net.URI=La proprietà {0} deve essere un URI valido +typeMismatch.java.util.Date=La proprietà {0} deve essere una data valida +typeMismatch.java.lang.Double=La proprietà {0} deve essere un numero valido +typeMismatch.java.lang.Integer=La proprietà {0} deve essere un numero valido +typeMismatch.java.lang.Long=La proprietà {0} deve essere un numero valido +typeMismatch.java.lang.Short=La proprietà {0} deve essere un numero valido +typeMismatch.java.math.BigDecimal=La proprietà {0} deve essere un numero valido +typeMismatch.java.math.BigInteger=La proprietà {0} deve essere un numero valido Index: grails-app/i18n/messages_ja.properties =================================================================== diff -u --- grails-app/i18n/messages_ja.properties (revision 0) +++ grails-app/i18n/messages_ja.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,55 @@ +default.doesnt.match.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]パターンと一致していません。 +default.invalid.url.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、有効なURLではありません。 +default.invalid.creditCard.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、有効なクレジットカード番号ではありません。 +default.invalid.email.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、有効なメールアドレスではありません。 +default.invalid.range.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]から[{4}]範囲内を指定してください。 +default.invalid.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]から[{4}]以内を指定してください。 +default.invalid.max.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最大値[{3}]より大きいです。 +default.invalid.min.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最小値[{3}]より小さいです。 +default.invalid.max.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最大値[{3}]より大きいです。 +default.invalid.min.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最小値[{3}]より小さいです。 +default.invalid.validator.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、カスタムバリデーションを通過できません。 +default.not.inlist.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]リスト内に存在しません。 +default.blank.message=[{1}]クラスのプロパティ[{0}]の空白は許可されません。 +default.not.equal.message=クラス[{1}]プロパティ[{0}]の値[{2}]に[{3}]は許可されません。 +default.null.message=[{1}]クラスのプロパティ[{0}]にnullは許可されません。 +default.not.unique.message=クラス[{1}]プロパティ[{0}]の値[{2}]は既に使用されています。 + +default.paginate.prev=戻る +default.paginate.next=次へ +default.boolean.true=はい +default.boolean.false=いいえ +default.date.format=yyyy/MM/dd HH:mm:ss z +default.number.format=0 + +default.created.message={0}(id:{1})を作成しました。 +default.updated.message={0}(id:{1})を更新しました。 +default.deleted.message={0}(id:{1})を削除しました。 +default.not.deleted.message={0}(id:{1})は削除できませんでした。 +default.not.found.message={0}(id:{1})は見つかりませんでした。 +default.optimistic.locking.failure=この{0}は編集中に他のユーザによって先に更新されています。 + +default.home.label=ホーム +default.list.label={0}リスト +default.add.label={0}を追加 +default.new.label={0}を新規作成 +default.create.label={0}を作成 +default.show.label={0}詳細 +default.edit.label={0}を編集 + +default.button.create.label=作成 +default.button.edit.label=編集 +default.button.update.label=更新 +default.button.delete.label=削除 +default.button.delete.confirm.message=本当に削除してよろしいですか? + +# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author) +typeMismatch.java.net.URL={0}は有効なURLでなければなりません。 +typeMismatch.java.net.URI={0}は有効なURIでなければなりません。 +typeMismatch.java.util.Date={0}は有効な日付でなければなりません。 +typeMismatch.java.lang.Double={0}は有効な数値でなければなりません。 +typeMismatch.java.lang.Integer={0}は有効な数値でなければなりません。 +typeMismatch.java.lang.Long={0}は有効な数値でなければなりません。 +typeMismatch.java.lang.Short={0}は有効な数値でなければなりません。 +typeMismatch.java.math.BigDecimal={0}は有効な数値でなければなりません。 +typeMismatch.java.math.BigInteger={0}は有効な数値でなければなりません。 Index: grails-app/i18n/messages_nb.properties =================================================================== diff -u --- grails-app/i18n/messages_nb.properties (revision 0) +++ grails-app/i18n/messages_nb.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,56 @@ +default.doesnt.match.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] overholder ikke mønsteret [{3}] +default.invalid.url.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke en gyldig URL +default.invalid.creditCard.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke et gyldig kredittkortnummer +default.invalid.email.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke en gyldig epostadresse +default.invalid.range.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke innenfor intervallet [{3}] til [{4}] +default.invalid.size.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke innenfor intervallet [{3}] til [{4}] +default.invalid.max.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] overstiger maksimumsverdien på [{3}] +default.invalid.min.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er under minimumsverdien på [{3}] +default.invalid.max.size.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] overstiger maksimumslengden på [{3}] +default.invalid.min.size.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er kortere enn minimumslengden på [{3}] +default.invalid.validator.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] overholder ikke den brukerdefinerte valideringen +default.not.inlist.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] finnes ikke i listen [{3}] +default.blank.message=Feltet [{0}] i klassen [{1}] kan ikke være tom +default.not.equal.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] kan ikke være [{3}] +default.null.message=Feltet [{0}] i klassen [{1}] kan ikke være null +default.not.unique.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] må være unik + +default.paginate.prev=Forrige +default.paginate.next=Neste +default.boolean.true=Ja +default.boolean.false=Nei +default.date.format=dd.MM.yyyy HH:mm:ss z +default.number.format=0 + +default.created.message={0} {1} opprettet +default.updated.message={0} {1} oppdatert +default.deleted.message={0} {1} slettet +default.not.deleted.message={0} {1} kunne ikke slettes +default.not.found.message={0} med id {1} ble ikke funnet +default.optimistic.locking.failure=En annen bruker har oppdatert denne {0} mens du redigerte + +default.home.label=Hjem +default.list.label={0}liste +default.add.label=Legg til {0} +default.new.label=Ny {0} +default.create.label=Opprett {0} +default.show.label=Vis {0} +default.edit.label=Endre {0} + +default.button.create.label=Opprett +default.button.edit.label=Endre +default.button.update.label=Oppdater +default.button.delete.label=Slett +default.button.delete.confirm.message=Er du sikker? + +# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author) +typeMismatch.java.net.URL=Feltet {0} må være en gyldig URL +typeMismatch.java.net.URI=Feltet {0} må være en gyldig URI +typeMismatch.java.util.Date=Feltet {0} må være en gyldig dato +typeMismatch.java.lang.Double=Feltet {0} må være et gyldig tall +typeMismatch.java.lang.Integer=Feltet {0} må være et gyldig heltall +typeMismatch.java.lang.Long=Feltet {0} må være et gyldig heltall +typeMismatch.java.lang.Short=Feltet {0} må være et gyldig heltall +typeMismatch.java.math.BigDecimal=Feltet {0} må være et gyldig tall +typeMismatch.java.math.BigInteger=Feltet {0} må være et gyldig heltall + Index: grails-app/i18n/messages_nl.properties =================================================================== diff -u --- grails-app/i18n/messages_nl.properties (revision 0) +++ grails-app/i18n/messages_nl.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,55 @@ +default.doesnt.match.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] komt niet overeen met het vereiste patroon [{3}] +default.invalid.url.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldige URL +default.invalid.creditCard.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldig credit card nummer +default.invalid.email.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldig e-mailadres +default.invalid.range.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] valt niet in de geldige waardenreeks van [{3}] tot [{4}] +default.invalid.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] valt niet in de geldige grootte van [{3}] tot [{4}] +default.invalid.max.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] overschrijdt de maximumwaarde [{3}] +default.invalid.min.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is minder dan de minimumwaarde [{3}] +default.invalid.max.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] overschrijdt de maximumgrootte van [{3}] +default.invalid.min.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is minder dan minimumgrootte van [{3}] +default.invalid.validator.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is niet geldig +default.not.inlist.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] komt niet voor in de lijst [{3}] +default.blank.message=Attribuut [{0}] van entiteit [{1}] mag niet leeg zijn +default.not.equal.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] mag niet gelijk zijn aan [{3}] +default.null.message=Attribuut [{0}] van entiteit [{1}] mag niet leeg zijn +default.not.unique.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] moet uniek zijn + +default.paginate.prev=Vorige +default.paginate.next=Volgende +default.boolean.true=Ja +default.boolean.false=Nee +default.date.format=dd-MM-yyyy HH:mm:ss z +default.number.format=0 + +default.created.message={0} {1} ingevoerd +default.updated.message={0} {1} gewijzigd +default.deleted.message={0} {1} verwijderd +default.not.deleted.message={0} {1} kon niet worden verwijderd +default.not.found.message={0} met id {1} kon niet worden gevonden +default.optimistic.locking.failure=Een andere gebruiker heeft deze {0} al gewijzigd + +default.home.label=Home +default.list.label={0} Overzicht +default.add.label=Toevoegen {0} +default.new.label=Invoeren {0} +default.create.label=Invoeren {0} +default.show.label=Details {0} +default.edit.label=Wijzigen {0} + +default.button.create.label=Invoeren +default.button.edit.label=Wijzigen +default.button.update.label=Opslaan +default.button.delete.label=Verwijderen +default.button.delete.confirm.message=Weet je het zeker? + +# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author) +typeMismatch.java.net.URL=Attribuut {0} is geen geldige URL +typeMismatch.java.net.URI=Attribuut {0} is geen geldige URI +typeMismatch.java.util.Date=Attribuut {0} is geen geldige datum +typeMismatch.java.lang.Double=Attribuut {0} is geen geldig nummer +typeMismatch.java.lang.Integer=Attribuut {0} is geen geldig nummer +typeMismatch.java.lang.Long=Attribuut {0} is geen geldig nummer +typeMismatch.java.lang.Short=Attribuut {0} is geen geldig nummer +typeMismatch.java.math.BigDecimal=Attribuut {0} is geen geldig nummer +typeMismatch.java.math.BigInteger=Attribuut {0} is geen geldig nummer Index: grails-app/i18n/messages_pl.properties =================================================================== diff -u --- grails-app/i18n/messages_pl.properties (revision 0) +++ grails-app/i18n/messages_pl.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,59 @@ +# +# Translated by Matthias Hryniszak - padcom@gmail.com +# + +default.doesnt.match.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie pasuje do wymaganego wzorca [{3}] +default.invalid.url.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] jest niepoprawnym adresem URL +default.invalid.creditCard.message=Właściwość [{0}] klasy [{1}] with value [{2}] nie jest poprawnym numerem karty kredytowej +default.invalid.email.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie jest poprawnym adresem e-mail +default.invalid.range.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie zawiera się zakładanym zakresie od [{3}] do [{4}] +default.invalid.size.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie zawiera się w zakładanym zakresie rozmiarów od [{3}] do [{4}] +default.invalid.max.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] przekracza maksymalną wartość [{3}] +default.invalid.min.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] jest mniejsza niż minimalna wartość [{3}] +default.invalid.max.size.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] przekracza maksymalny rozmiar [{3}] +default.invalid.min.size.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] jest mniejsza niż minimalny rozmiar [{3}] +default.invalid.validator.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie spełnia założonych niestandardowych warunków +default.not.inlist.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie zawiera się w liście [{3}] +default.blank.message=Właściwość [{0}] klasy [{1}] nie może być pusta +default.not.equal.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie może równać się [{3}] +default.null.message=Właściwość [{0}] klasy [{1}] nie może być null +default.not.unique.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] musi być unikalna + +default.paginate.prev=Poprzedni +default.paginate.next=Następny +default.boolean.true=Prawda +default.boolean.false=Fałsz +default.date.format=yyyy-MM-dd HH:mm:ss z +default.number.format=0 + +default.created.message=Utworzono {0} {1} +default.updated.message=Zaktualizowano {0} {1} +default.deleted.message=Usunięto {0} {1} +default.not.deleted.message={0} {1} nie mógł zostać usunięty +default.not.found.message=Nie znaleziono {0} o id {1} +default.optimistic.locking.failure=Inny użytkownik zaktualizował ten obiekt {0} w trakcie twoich zmian + +default.home.label=Strona domowa +default.list.label=Lista {0} +default.add.label=Dodaj {0} +default.new.label=Utwórz {0} +default.create.label=Utwórz {0} +default.show.label=Pokaż {0} +default.edit.label=Edytuj {0} + +default.button.create.label=Utwórz +default.button.edit.label=Edytuj +default.button.update.label=Zaktualizuj +default.button.delete.label=Usuń +default.button.delete.confirm.message=Czy jesteś pewien? + +# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author) +typeMismatch.java.net.URL=Właściwość {0} musi być poprawnym adresem URL +typeMismatch.java.net.URI=Właściwość {0} musi być poprawnym adresem URI +typeMismatch.java.util.Date=Właściwość {0} musi być poprawną datą +typeMismatch.java.lang.Double=Właściwość {0} musi być poprawnyą liczbą +typeMismatch.java.lang.Integer=Właściwość {0} musi być poprawnyą liczbą +typeMismatch.java.lang.Long=Właściwość {0} musi być poprawnyą liczbą +typeMismatch.java.lang.Short=Właściwość {0} musi być poprawnyą liczbą +typeMismatch.java.math.BigDecimal=Właściwość {0} musi być poprawnyą liczbą +typeMismatch.java.math.BigInteger=Właściwość {0} musi być poprawnyą liczbą Index: grails-app/i18n/messages_pt_BR.properties =================================================================== diff -u --- grails-app/i18n/messages_pt_BR.properties (revision 0) +++ grails-app/i18n/messages_pt_BR.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,59 @@ +# +# Translated by Lucas Teixeira - lucastex@gmail.com +# + +default.doesnt.match.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atende ao padrão definido [{3}] +default.invalid.url.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é uma URL válida +default.invalid.creditCard.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um número válido de cartão de crédito +default.invalid.email.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um endereço de email válido. +default.invalid.range.message=O campo [{0}] da classe [{1}] com o valor [{2}] não está entre a faixa de valores válida de [{3}] até [{4}] +default.invalid.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] não está na faixa de tamanho válida de [{3}] até [{4}] +default.invalid.max.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o valor máximo [{3}] +default.invalid.min.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o valor mínimo [{3}] +default.invalid.max.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o tamanho máximo de [{3}] +default.invalid.min.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o tamanho mínimo de [{3}] +default.invalid.validator.message=O campo [{0}] da classe [{1}] com o valor [{2}] não passou na validação +default.not.inlist.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um valor dentre os permitidos na lista [{3}] +default.blank.message=O campo [{0}] da classe [{1}] não pode ficar em branco +default.not.equal.message=O campo [{0}] da classe [{1}] com o valor [{2}] não pode ser igual a [{3}] +default.null.message=O campo [{0}] da classe [{1}] não pode ser vazio +default.not.unique.message=O campo [{0}] da classe [{1}] com o valor [{2}] deve ser único + +default.paginate.prev=Anterior +default.paginate.next=Próximo +default.boolean.true=Sim +default.boolean.false=Não +default.date.format=dd/MM/yyyy HH:mm:ss z +default.number.format=0 + +default.created.message={0} {1} criado +default.updated.message={0} {1} atualizado +default.deleted.message={0} {1} removido +default.not.deleted.message={0} {1} não pode ser removido +default.not.found.message={0} não foi encontrado com o id {1} +default.optimistic.locking.failure=Outro usuário atualizou este [{0}] enquanto você tentou salvá-lo + +default.home.label=Principal +default.list.label={0} Listagem +default.add.label=Adicionar {0} +default.new.label=Novo {0} +default.create.label=Criar {0} +default.show.label=Ver {0} +default.edit.label=Editar {0} + +default.button.create.label=Criar +default.button.edit.label=Editar +default.button.update.label=Alterar +default.button.delete.label=Remover +default.button.delete.confirm.message=Tem certeza? + +# Mensagens de erro em atribuição de valores. Use "typeMismatch.$className.$propertyName" para customizar (eg typeMismatch.Book.author) +typeMismatch.java.net.URL=O campo {0} deve ser uma URL válida. +typeMismatch.java.net.URI=O campo {0} deve ser uma URI válida. +typeMismatch.java.util.Date=O campo {0} deve ser uma data válida +typeMismatch.java.lang.Double=O campo {0} deve ser um número válido. +typeMismatch.java.lang.Integer=O campo {0} deve ser um número válido. +typeMismatch.java.lang.Long=O campo {0} deve ser um número válido. +typeMismatch.java.lang.Short=O campo {0} deve ser um número válido. +typeMismatch.java.math.BigDecimal=O campo {0} deve ser um número válido. +typeMismatch.java.math.BigInteger=O campo {0} deve ser um número válido. Index: grails-app/i18n/messages_pt_PT.properties =================================================================== diff -u --- grails-app/i18n/messages_pt_PT.properties (revision 0) +++ grails-app/i18n/messages_pt_PT.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,34 @@ +# +# translation by miguel.ping@gmail.com, based on pt_BR translation by Lucas Teixeira - lucastex@gmail.com +# + +default.doesnt.match.message=O campo [{0}] da classe [{1}] com o valor [{2}] não corresponde ao padrão definido [{3}] +default.invalid.url.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um URL válido +default.invalid.creditCard.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um número válido de cartão de crédito +default.invalid.email.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um endereço de email válido. +default.invalid.range.message=O campo [{0}] da classe [{1}] com o valor [{2}] não está dentro dos limites de valores válidos de [{3}] a [{4}] +default.invalid.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] está fora dos limites de tamanho válido de [{3}] a [{4}] +default.invalid.max.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o valor máximo [{3}] +default.invalid.min.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o valor mínimo [{3}] +default.invalid.max.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o tamanho máximo de [{3}] +default.invalid.min.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o tamanho mínimo de [{3}] +default.invalid.validator.message=O campo [{0}] da classe [{1}] com o valor [{2}] não passou na validação +default.not.inlist.message=O campo [{0}] da classe [{1}] com o valor [{2}] não se encontra nos valores permitidos da lista [{3}] +default.blank.message=O campo [{0}] da classe [{1}] não pode ser vazio +default.not.equal.message=O campo [{0}] da classe [{1}] com o valor [{2}] não pode ser igual a [{3}] +default.null.message=O campo [{0}] da classe [{1}] não pode ser vazio +default.not.unique.message=O campo [{0}] da classe [{1}] com o valor [{2}] deve ser único + +default.paginate.prev=Anterior +default.paginate.next=Próximo + +# Mensagens de erro em atribuição de valores. Use "typeMismatch.$className.$propertyName" para personalizar(eg typeMismatch.Book.author) +typeMismatch.java.net.URL=O campo {0} deve ser um URL válido. +typeMismatch.java.net.URI=O campo {0} deve ser um URI válido. +typeMismatch.java.util.Date=O campo {0} deve ser uma data válida +typeMismatch.java.lang.Double=O campo {0} deve ser um número válido. +typeMismatch.java.lang.Integer=O campo {0} deve ser um número válido. +typeMismatch.java.lang.Long=O campo {0} deve ser um número valido. +typeMismatch.java.lang.Short=O campo {0} deve ser um número válido. +typeMismatch.java.math.BigDecimal=O campo {0} deve ser um número válido. +typeMismatch.java.math.BigInteger=O campo {0} deve ser um número válido. Index: grails-app/i18n/messages_ru.properties =================================================================== diff -u --- grails-app/i18n/messages_ru.properties (revision 0) +++ grails-app/i18n/messages_ru.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,31 @@ +default.doesnt.match.message=Значение [{2}] поля [{0}] класса [{1}] не соответствует образцу [{3}] +default.invalid.url.message=Значение [{2}] поля [{0}] класса [{1}] не является допустимым URL-адресом +default.invalid.creditCard.message=Значение [{2}] поля [{0}] класса [{1}] не является допустимым номером кредитной карты +default.invalid.email.message=Значение [{2}] поля [{0}] класса [{1}] не является допустимым e-mail адресом +default.invalid.range.message=Значение [{2}] поля [{0}] класса [{1}] не попадает в допустимый интервал от [{3}] до [{4}] +default.invalid.size.message=Размер поля [{0}] класса [{1}] (значение: [{2}]) не попадает в допустимый интервал от [{3}] до [{4}] +default.invalid.max.message=Значение [{2}] поля [{0}] класса [{1}] больше чем максимально допустимое значение [{3}] +default.invalid.min.message=Значение [{2}] поля [{0}] класса [{1}] меньше чем минимально допустимое значение [{3}] +default.invalid.max.size.message=Размер поля [{0}] класса [{1}] (значение: [{2}]) больше чем максимально допустимый размер [{3}] +default.invalid.min.size.message=Размер поля [{0}] класса [{1}] (значение: [{2}]) меньше чем минимально допустимый размер [{3}] +default.invalid.validator.message=Значение [{2}] поля [{0}] класса [{1}] не допустимо +default.not.inlist.message=Значение [{2}] поля [{0}] класса [{1}] не попадает в список допустимых значений [{3}] +default.blank.message=Поле [{0}] класса [{1}] не может быть пустым +default.not.equal.message=Значение [{2}] поля [{0}] класса [{1}] не может быть равно [{3}] +default.null.message=Поле [{0}] класса [{1}] не может иметь значение null +default.not.unique.message=Значение [{2}] поля [{0}] класса [{1}] должно быть уникальным + +default.paginate.prev=Предыдушая страница +default.paginate.next=Следующая страница + +# Ошибки при присвоении данных. Для точной настройки для полей классов используйте +# формат "typeMismatch.$className.$propertyName" (например, typeMismatch.Book.author) +typeMismatch.java.net.URL=Значение поля {0} не является допустимым URL +typeMismatch.java.net.URI=Значение поля {0} не является допустимым URI +typeMismatch.java.util.Date=Значение поля {0} не является допустимой датой +typeMismatch.java.lang.Double=Значение поля {0} не является допустимым числом +typeMismatch.java.lang.Integer=Значение поля {0} не является допустимым числом +typeMismatch.java.lang.Long=Значение поля {0} не является допустимым числом +typeMismatch.java.lang.Short=Значение поля {0} не является допустимым числом +typeMismatch.java.math.BigDecimal=Значение поля {0} не является допустимым числом +typeMismatch.java.math.BigInteger=Значение поля {0} не является допустимым числом Index: grails-app/i18n/messages_sv.properties =================================================================== diff -u --- grails-app/i18n/messages_sv.properties (revision 0) +++ grails-app/i18n/messages_sv.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,55 @@ +default.doesnt.match.message=Attributet [{0}] för klassen [{1}] med värde [{2}] matchar inte mot uttrycket [{3}] +default.invalid.url.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte en giltig URL +default.invalid.creditCard.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte ett giltigt kreditkortsnummer +default.invalid.email.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte en giltig e-postadress +default.invalid.range.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte inom intervallet [{3}] till [{4}] +default.invalid.size.message=Attributet [{0}] för klassen [{1}] med värde [{2}] har en storlek som inte är inom [{3}] till [{4}] +default.invalid.max.message=Attributet [{0}] för klassen [{1}] med värde [{2}] överskrider maxvärdet [{3}] +default.invalid.min.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är mindre än minimivärdet [{3}] +default.invalid.max.size.message=Attributet [{0}] för klassen [{1}] med värde [{2}] överskrider maxstorleken [{3}] +default.invalid.min.size.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är mindre än minimistorleken [{3}] +default.invalid.validator.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte giltigt enligt anpassad regel +default.not.inlist.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte giltigt, måste vara ett av [{3}] +default.blank.message=Attributet [{0}] för klassen [{1}] får inte vara tomt +default.not.equal.message=Attributet [{0}] för klassen [{1}] med värde [{2}] får inte vara lika med [{3}] +default.null.message=Attributet [{0}] för klassen [{1}] får inte vara tomt +default.not.unique.message=Attributet [{0}] för klassen [{1}] med värde [{2}] måste vara unikt + +default.paginate.prev=Föregående +default.paginate.next=Nästa +default.boolean.true=Sant +default.boolean.false=Falskt +default.date.format=yyyy-MM-dd HH:mm:ss z +default.number.format=0 + +default.created.message={0} {1} skapades +default.updated.message={0} {1} uppdaterades +default.deleted.message={0} {1} borttagen +default.not.deleted.message={0} {1} kunde inte tas bort +default.not.found.message={0} med id {1} kunde inte hittas +default.optimistic.locking.failure=En annan användare har uppdaterat det här {0} objektet medan du redigerade det + +default.home.label=Hem +default.list.label= {0} - Lista +default.add.label=Lägg till {0} +default.new.label=Skapa {0} +default.create.label=Skapa {0} +default.show.label=Visa {0} +default.edit.label=Ändra {0} + +default.button.create.label=Skapa +default.button.edit.label=Ändra +default.button.update.label=Uppdatera +default.button.delete.label=Ta bort +default.button.delete.confirm.message=Är du säker? + +# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author) +typeMismatch.java.net.URL=Värdet för {0} måste vara en giltig URL +typeMismatch.java.net.URI=Värdet för {0} måste vara en giltig URI +typeMismatch.java.util.Date=Värdet {0} måste vara ett giltigt datum +typeMismatch.java.lang.Double=Värdet {0} måste vara ett giltigt nummer +typeMismatch.java.lang.Integer=Värdet {0} måste vara ett giltigt heltal +typeMismatch.java.lang.Long=Värdet {0} måste vara ett giltigt heltal +typeMismatch.java.lang.Short=Värdet {0} måste vara ett giltigt heltal +typeMismatch.java.math.BigDecimal=Värdet {0} måste vara ett giltigt nummer +typeMismatch.java.math.BigInteger=Värdet {0} måste vara ett giltigt heltal \ No newline at end of file Index: grails-app/i18n/messages_th.properties =================================================================== diff -u --- grails-app/i18n/messages_th.properties (revision 0) +++ grails-app/i18n/messages_th.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,55 @@ +default.doesnt.match.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบที่กำหนดไว้ใน [{3}] +default.invalid.url.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบ URL +default.invalid.creditCard.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบหมายเลขบัตรเครดิต +default.invalid.email.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบอีเมล์ +default.invalid.range.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้มีค่าที่ถูกต้องในช่วงจาก [{3}] ถึง [{4}] +default.invalid.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้มีขนาดที่ถูกต้องในช่วงจาก [{3}] ถึง [{4}] +default.invalid.max.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีค่าเกิดกว่าค่ามากสุด [{3}] +default.invalid.min.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีค่าน้อยกว่าค่าต่ำสุด [{3}] +default.invalid.max.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีขนาดเกินกว่าขนาดมากสุดของ [{3}] +default.invalid.min.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีขนาดต่ำกว่าขนาดต่ำสุดของ [{3}] +default.invalid.validator.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ผ่านการทวนสอบค่าที่ตั้งขึ้น +default.not.inlist.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้อยู่ในรายการต่อไปนี้ [{3}] +default.blank.message=คุณสมบัติ [{0}] ของคลาส [{1}] ไม่สามารถเป็นค่าว่างได้ +default.not.equal.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่สามารถเท่ากับ [{3}] ได้ +default.null.message=คุณสมบัติ [{0}] ของคลาส [{1}] ไม่สามารถเป็น null ได้ +default.not.unique.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] จะต้องไม่ซ้ำ (unique) + +default.paginate.prev=ก่อนหน้า +default.paginate.next=ถัดไป +default.boolean.true=จริง +default.boolean.false=เท็จ +default.date.format=dd-MM-yyyy HH:mm:ss z +default.number.format=0 + +default.created.message=สร้าง {0} {1} เรียบร้อยแล้ว +default.updated.message=ปรับปรุง {0} {1} เรียบร้อยแล้ว +default.deleted.message=ลบ {0} {1} เรียบร้อยแล้ว +default.not.deleted.message=ไม่สามารถลบ {0} {1} +default.not.found.message=ไม่พบ {0} ด้วย id {1} นี้ +default.optimistic.locking.failure=มีผู้ใช้ท่านอื่นปรับปรุง {0} ขณะที่คุณกำลังแก้ไขข้อมูลอยู่ + +default.home.label=หน้าแรก +default.list.label=รายการ {0} +default.add.label=เพิ่ม {0} +default.new.label=สร้าง {0} ใหม่ +default.create.label=สร้าง {0} +default.show.label=แสดง {0} +default.edit.label=แก้ไข {0} + +default.button.create.label=สร้าง +default.button.edit.label=แก้ไข +default.button.update.label=ปรับปรุง +default.button.delete.label=ลบ +default.button.delete.confirm.message=คุณแน่ใจหรือไม่ ? + +# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author) +typeMismatch.java.net.URL=คุณสมบัติ '{0}' จะต้องเป็นค่า URL ที่ถูกต้อง +typeMismatch.java.net.URI=คุณสมบัติ '{0}' จะต้องเป็นค่า URI ที่ถูกต้อง +typeMismatch.java.util.Date=คุณสมบัติ '{0}' จะต้องมีค่าเป็นวันที่ +typeMismatch.java.lang.Double=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Double +typeMismatch.java.lang.Integer=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Integer +typeMismatch.java.lang.Long=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Long +typeMismatch.java.lang.Short=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Short +typeMismatch.java.math.BigDecimal=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท BigDecimal +typeMismatch.java.math.BigInteger=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท BigInteger Index: grails-app/i18n/messages_zh_CN.properties =================================================================== diff -u --- grails-app/i18n/messages_zh_CN.properties (revision 0) +++ grails-app/i18n/messages_zh_CN.properties (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,18 @@ +default.blank.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u4E0D\u80FD\u4E3A\u7A7A +default.doesnt.match.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0E\u5B9A\u4E49\u7684\u6A21\u5F0F [{3}]\u4E0D\u5339\u914D +default.invalid.creditCard.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684\u4FE1\u7528\u5361\u53F7 +default.invalid.email.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684\u7535\u5B50\u90AE\u4EF6\u5730\u5740 +default.invalid.max.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u6BD4\u6700\u5927\u503C [{3}]\u8FD8\u5927 +default.invalid.max.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u6BD4\u6700\u5927\u503C [{3}]\u8FD8\u5927 +default.invalid.min.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u6BD4\u6700\u5C0F\u503C [{3}]\u8FD8\u5C0F +default.invalid.min.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u6BD4\u6700\u5C0F\u503C [{3}]\u8FD8\u5C0F +default.invalid.range.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u5728\u5408\u6CD5\u7684\u8303\u56F4\u5185( [{3}] \uFF5E [{4}] ) +default.invalid.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u4E0D\u5728\u5408\u6CD5\u7684\u8303\u56F4\u5185( [{3}] \uFF5E [{4}] ) +default.invalid.url.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684URL +default.invalid.validator.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u672A\u80FD\u901A\u8FC7\u81EA\u5B9A\u4E49\u7684\u9A8C\u8BC1 +default.not.equal.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0E[{3}]\u4E0D\u76F8\u7B49 +default.not.inlist.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u5728\u5217\u8868\u7684\u53D6\u503C\u8303\u56F4\u5185 +default.not.unique.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u5FC5\u987B\u662F\u552F\u4E00\u7684 +default.null.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u4E0D\u80FD\u4E3Anull +default.paginate.next=\u4E0B\u9875 +default.paginate.prev=\u4E0A\u9875 Index: grails-app/init/BootStrap.groovy =================================================================== diff -u --- grails-app/init/BootStrap.groovy (revision 0) +++ grails-app/init/BootStrap.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,8 @@ +class BootStrap { + + def init = { servletContext -> + } + + def destroy = { + } +} Index: grails-app/init/ds/service/Application.groovy =================================================================== diff -u --- grails-app/init/ds/service/Application.groovy (revision 0) +++ grails-app/init/ds/service/Application.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,10 @@ +package ds.service + +import grails.boot.GrailsApp +import grails.boot.config.GrailsAutoConfiguration + +class Application extends GrailsAutoConfiguration { + static void main(String[] args) { + GrailsApp.run(Application, args) + } +} Index: grails-app/jobs/com/lemans/ds/importing/BulkImportJob.groovy =================================================================== diff -u --- grails-app/jobs/com/lemans/ds/importing/BulkImportJob.groovy (revision 0) +++ grails-app/jobs/com/lemans/ds/importing/BulkImportJob.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,22 @@ +package com.lemans.ds.importing + +import com.lemans.services.LemansManager +import org.quartz.JobExecutionContext + + +class BulkImportJob extends LemansManager { + + def bulkLocaleImportProcessService + + def bulkImportService + + static triggers = { } + + def execute(JobExecutionContext context) { + Integer processId = context.mergedJobDataMap.get('processId') + LocaleImportProcess localeImportProcess = bulkImportService.findLocaleImportProcess(processId) + if (localeImportProcess) { + bulkLocaleImportProcessService.triggerProcess(localeImportProcess) + } + } +} Index: grails-app/jobs/com/lemans/ds/splitvalidation/ProductSplitValidationJob.groovy =================================================================== diff -u --- grails-app/jobs/com/lemans/ds/splitvalidation/ProductSplitValidationJob.groovy (revision 0) +++ grails-app/jobs/com/lemans/ds/splitvalidation/ProductSplitValidationJob.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,21 @@ +package com.lemans.ds.splitvalidation + +import com.lemans.services.LemansManager +import org.quartz.DisallowConcurrentExecution + +@DisallowConcurrentExecution +class ProductSplitValidationJob extends LemansManager { + + + def jobStatusManagerService + + static triggers = { + cron startDelay: 15000, cronExpression: '0 0/30 * * * ?' + } + + def concurrent = false + + def execute() { + jobStatusManagerService.execute() + } +} Index: grails-app/services/com/lemans/common/CommonService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/common/CommonService.groovy (revision 0) +++ grails-app/services/com/lemans/common/CommonService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,36 @@ +package com.lemans.common + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +/** + * CommonService provides miscellaneous query operations that are common, + * i.e. looking up reference data, etc. + * + */ +@Transactional(readOnly = true) +class CommonService extends LemansService { + + /** + * Finds a SubComCode by code. + * + * @param criteria + * + * @return Map containing SubComCode info + */ + Map findSubComCode(Map criteria) { + dqx(criteria).executeOneFrom('dbo.vwSubComCode', ['subComCode = :subComCode']).results[0] + } + + /** + * Finds SubComCodes, currently All. + * + * @param criteria + * + * @return List containing subComCodes + */ + List findSubComCodes(Map criteria) { + criteria.sorting = criteria.sorting ?: 'subComCode+ASC' + dqx(criteria).executeFrom('dbo.vwSubComCode', []).results + } +} Index: grails-app/services/com/lemans/ds/attribute/AttributeNameManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/attribute/AttributeNameManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/attribute/AttributeNameManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,86 @@ +package com.lemans.ds.attribute + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class AttributeNameManagerService extends LemansManager { + + def attributeNameService + + /** + * Updates attributeName. + + * @return AttributeName which may have errors or be null + */ + AttributeName updateAttributeName(Map values, Integer id, String username) { + AttributeName attributeName = findDomainObjectById(AttributeName, id) + if (attributeName) { + applyValuesToDomain(values, attributeName) + attributeName.validate() + saveOrDiscardDomain(attributeName, username) + } + } + + /** + * Creates a new AttributeName. + * + * @param values + * @param username + * + * @return AttributeName which may contain errors or be null + */ + AttributeName createAttributeName(Map values, String username) { + AttributeName attributeName = new AttributeName(values) + attributeName.validate() + saveOrDiscardDomain(attributeName, username) + } + + /** + * Creates a new AttributeNameLocale. + * + * @param values + * @param username + * + * @return AttributeNameLocale which may contain errors or be null + */ + AttributeNameLocale updateAttributeNameWithLocale(Map values, String username) { + AttributeNameLocale attributeNameLocale = AttributeNameLocale.findByAttributeNameIdAndLocale(values.attributeNameId, values.locale) + if (!attributeNameLocale) { + AttributeName attributeName = AttributeName.findById(values.attributeNameId) + if (attributeName) { + attributeNameLocale = new AttributeNameLocale([attributeNameId: attributeName.id]) + } else { + return + } + } + applyValuesToDomain(values, attributeNameLocale) + attributeNameLocale.validate() + saveOrDiscardDomain(attributeNameLocale, username) + } + + /** + * Soft deletes a AttributeName. + * + * @param attributeNameId + * @param username + * + * @return DomainForm which may errors or be null + */ + AttributeName deleteAttributeName(Integer id, String username) { + AttributeName attributeName + Map attributeNameMap = attributeNameService.findAttributeNameById([attributeNameId: id]) + if (attributeNameMap) { + if (attributeNameMap.canDelete) { + attributeName = findDomainObjectById(AttributeName, id) + if (attributeName) { + softDelete(attributeName, username) + } + } else { + attributeName = new AttributeName() + attributeName.errors.reject('invalid', 'Cannot delete AttributeName when assigned to Category or Part') + } + } + attributeName + } +} Index: grails-app/services/com/lemans/ds/attribute/AttributeNameService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/attribute/AttributeNameService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/attribute/AttributeNameService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,41 @@ +package com.lemans.ds.attribute + + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional +class AttributeNameService extends LemansService { + + /** + * Find a attribute by attributeNameId. + * + * @param criteria containing attributeNameId + * + * @return attribute + */ + Map findAttributeNameById(Map criteria) { + List clauses = ['attributeNameId = :attributeNameId'] + dqx(criteria).executeOneFromLocale('dbo.vwAttributeName', clauses)?.results[0] + } + + /** + * Get collection of Attribute Name + * + * @param criteria containing pageSize and sorting params + * + * @return Map containing results and totalRecords + */ + Map findAttributeNames(Map criteria) { + criteria.sorting = criteria.sorting ?: localeName(criteria, 'attributeName') + dqx(criteria).executeFromLocale('dbo.vwAttributeName') + } + + private String localeName(Map criteria, String name) { + name + (localeExists(criteria) ? 'Locale' : '') + } + + private boolean localeExists(Map criteria) { + criteria.locale && criteria.locale != Locale.default.toString() + } +} Index: grails-app/services/com/lemans/ds/attribute/AttributeService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/attribute/AttributeService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/attribute/AttributeService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,103 @@ +package com.lemans.ds.attribute + +import com.lemans.services.LemansService +import groovy.json.JsonSlurper +import org.json.XML + +/** + * Created by vramisetti on 8/18/2017. + */ +class AttributeService extends LemansService { + + private static final String ATTR_FROM = 'FROM vwPartAttribute x INNER JOIN vwPart p ON x.partNumber = p.partNumber' + + private static final String ATTR_GROUP_BY = '''p.partNumber, x.partAttributeId, x.attributeNameId, x.attributeName, x.attributeValueId, +x.attributeValue, x.partNumber, p.punctuatedPartNumber, p.categoryId, p.partDescr, p.categoryName, p.mediaUrl, p.qualifiedCategoryName''' + + /** + * Find parts by attribute name. + * + * @param criteria containing attributeNameId, attributeValueId and categoryId + * + * @return Map containing results and totalRecords + */ + Map partsByAttributeName(Map criteria) { + partsByAttribute(criteria + [attrSelect: 'x.attributeNameId, x.attributeName', + groupingAttrSelect: 'pa.attributeValue, pa.attributeValueId', groupingAttrName: 'attributeValues']) + } + + /** + * Find parts by attribute value. + * + * @param criteria containing attributeNameId, attributeValueId and categoryId + * + * @return Map containing results and totalRecords + */ + Map partsByAttributeValue(Map criteria) { + partsByAttribute(criteria + [attrSelect: 'x.attributeValueId, x.attributeValue', + groupingAttrSelect: 'pa.attributeName, pa.attributeNameId', groupingAttrName: 'attributeNames']) + } + + private Map partsByAttribute(Map criteria) { + criteria.sorting = criteria.sorting ?: 'x.partAttributeId' + criteria.groupBy = ATTR_GROUP_BY + Map data = dqx(criteria).executeSelectFrom(selectQuery(criteria), ATTR_FROM, partsAttributeClauses(criteria)) + addGroupingAttr(criteria, data) + } + + private String selectQuery(Map criteria) { + """SELECT ${criteria.attrSelect}, x.partNumber, p.punctuatedPartNumber, p.categoryId, p.partDescr, p.categoryName, p.mediaUrl, + p.qualifiedCategoryName, ( + SELECT DISTINCT ${criteria.groupingAttrSelect} + FROM vwPartAttribute pa + WHERE pa.partNumber = p.partNumber AND ${criteria.attributeNameId ? + "pa.attributeNameId = ${criteria.attributeNameId}" : "pa.attributeValueId = ${criteria.attributeValueId}"} + FOR XML PATH('attributes')) AS attributes""" + } + + private Map addGroupingAttr(Map criteria, Map data) { + JsonSlurper slurper = new JsonSlurper() + data.results.each { + it[criteria.groupingAttrName] = slurper.parseText(XML.toJSONArray(it.attributes.characterStream.text).toString()).attributes + it.remove('attributes') + } + data + } + + /** + * Find categories by attributeValue + * + * @param criteria containing attributeValueId + * + * @return Map containing results and totalRecords + */ + Map categoriesByAttributeValue(Map criteria) { + criteria.sorting = criteria.sorting ?: 'categoryAttributeValueId' + dqx(criteria).executeFrom('vwCategoryAttributeValue', attributeClauses(criteria)) + } + + /** + * Find categories by attributeName + * + * @param criteria containing attributeNameId + * + * @return Map containing results and totalRecords + */ + Map categoriesByAttributeName(Map criteria) { + criteria.sorting = criteria.sorting ?: 'categoryAttributeId' + dqx(criteria).executeFrom('vwCategoryAttribute', attributeClauses(criteria)) + } + + private List partsAttributeClauses(Map criteria) { + List clauses = attributeClauses(criteria) + if (criteria.categoryId) { clauses << 'p.categoryId = :categoryId' } + clauses + } + + private List attributeClauses(Map criteria) { + List clauses = [] + if (criteria.attributeNameId) { clauses << 'x.attributeNameId = :attributeNameId' } + if (criteria.attributeValueId) { clauses << 'x.attributeValueId = :attributeValueId' } + clauses + } +} Index: grails-app/services/com/lemans/ds/attribute/AttributeValueManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/attribute/AttributeValueManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/attribute/AttributeValueManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,86 @@ +package com.lemans.ds.attribute + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class AttributeValueManagerService extends LemansManager { + + def attributeValueService + + /** + * Updates attributeValue. + + * @return AttributeValue which may have errors or be null + */ + AttributeValue updateAttributeValue(Map values, String username) { + AttributeValue attributeValue = findDomainObjectById(AttributeValue, values.attributeValueId) + if (attributeValue) { + applyValuesToDomain(values, attributeValue) + attributeValue.validate() + saveOrDiscardDomain(attributeValue, username) + } + } + + /** + * Creates a new AttributeValue. + * + * @param values + * @param username + * + * @return AttributeValue which may contain errors or be null + */ + AttributeValue createAttributeValue(Map values, String username) { + AttributeValue attributeValue = new AttributeValue(values) + attributeValue.validate() + saveOrDiscardDomain(attributeValue, username) + } + + /** + * Creates a new AttributeValue and AttributeValueLocale. + * + * @param values + * @param username + * + * @return AttributeValueLocale which may contain errors or be null + */ + AttributeValueLocale updateAttributeValueWithLocale(Map values, String username) { + AttributeValueLocale attributeValueLocale = AttributeValueLocale.findByAttributeValueIdAndLocale(values.attributeValueId, values.locale) + if (!attributeValueLocale) { + AttributeValue attributeValue = AttributeValue.findById(values.attributeValueId) + if (attributeValue) { + attributeValueLocale = new AttributeValueLocale([attributeValueId: attributeValue.id]) + } else { + return + } + } + applyValuesToDomain(values, attributeValueLocale) + attributeValueLocale.validate() + saveOrDiscardDomain(attributeValueLocale, username) + } + + /** + * Soft deletes a AttributeValue. + * + * @param attributeValueId + * @param username + * + * @return DomainForm which may errors or be null + */ + AttributeValue deleteAttributeValue(Integer id, String username) { + AttributeValue attributeValue + Map attributeValueMap = attributeValueService.findAttributeValueById([attributeValueId: id]) + if (attributeValueMap) { + if (attributeValueMap.canDelete) { + attributeValue = findDomainObjectById(AttributeValue, id) + if (attributeValue) { + softDelete(attributeValue, username) + } + } else { + attributeValue = new AttributeValue() + attributeValue.errors.reject('invalid', 'Cannot delete AttributeValue when assigned to Category or Part') + } + } + attributeValue + } +} Index: grails-app/services/com/lemans/ds/attribute/AttributeValueService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/attribute/AttributeValueService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/attribute/AttributeValueService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,79 @@ +package com.lemans.ds.attribute + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional +class AttributeValueService extends LemansService { + + private static final String VW_CATEGORY_ATTRIBUTE_VALUE = 'SELECT DISTINCT cav.attributeValueId, cav.attributeValue FROM ' + + 'dbo.vwCategoryAttributeValue cav WHERE cav.attributeNameId = ?' + + /** + * Find a attribute by attributeValueId. + * + * @param criteria containing attributeValueId + * + * @return attribute + */ + def findAttributeValueById(Map criteria) { + List clauses = ['attributeValueId = :attributeValueId'] + dqx(criteria).executeOneFromLocale('dbo.vwAttributeValue', clauses)?.results[0] + } + + /** + * Get collection of Attribute Name + * + * @param criteria containing pageSize and sorting params + * + * @return Map containing results and totalRecords + */ + Map findAttributeValues(Map criteria) { + criteria.sorting = criteria.sorting ?: localeName(criteria, 'attributeValue') + dqx(criteria).executeFromLocale('dbo.vwAttributeValue', qClauses(criteria)) + } + + /** + * Gets collection of Attribute values by Attribute Name Id + * + * @param attributeNameId + * + * @return Map containing results + */ + Map findAttributeValuesByAttributeNameId(Integer attributeNameId) { + [results: sql().rows(VW_CATEGORY_ATTRIBUTE_VALUE, [attributeNameId])] + } + + private List qClauses(criteria) { + List clauses = [] + if (criteria.q) { + expandQ(criteria) + criteria.sort == null + if (localeExists(criteria)) { + criteria.sorting = '''CASE WHEN attributeValueLocale = :q THEN 1 + WHEN attributeValueLocale LIKE :beginningWithQ THEN 2 ELSE 3 END''' + clauses << 'attributeValueLocale LIKE :containingQ' + } else { + criteria.sorting = '''CASE WHEN attributeValue = :q THEN 1 + WHEN attributeValue LIKE :beginningWithQ THEN 2 ELSE 3 END''' + clauses << 'attributeValue LIKE :containingQ' + } + } + clauses + } + + private expandQ(criteria) { + criteria.with { + beginningWithQ = "$q%".toString() + containingQ = "%$q%".toString() + } + } + + private String localeName(Map criteria, String name) { + name + (localeExists(criteria) ? 'Locale' : '') + } + + private boolean localeExists(Map criteria) { + criteria.locale && criteria.locale != Locale.default.toString() + } +} Index: grails-app/services/com/lemans/ds/bulk/BulkLocaleImportProcessService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/bulk/BulkLocaleImportProcessService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/bulk/BulkLocaleImportProcessService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,133 @@ +package com.lemans.ds.bulk + +import com.lemans.ds.bulk.excel.ExcelBuilderHelper +import com.lemans.ds.bulk.excel.ExcelParserHelper +import com.lemans.ds.importing.LocaleImportProcess +import com.lemans.services.LemansManager +import org.apache.poi.POIXMLException +import org.apache.poi.hssf.usermodel.HSSFWorkbook +import org.apache.poi.openxml4j.exceptions.InvalidFormatException +import org.apache.poi.ss.usermodel.Workbook + +import javax.annotation.Resource + +class BulkLocaleImportProcessService extends LemansManager { + + def bulkProductFeatureService + + def bulkProductService + + def bulkCategoryAttributeValueService + + def bulkPartService + + @Resource(name = 'dsLocaleImport') + def dsLocaleImport + + private static + final List HEADER_IDENTIFIER_FOR_CATEGORY_ATTRIBUTE_VALUE = ['Category Id', 'Attribute Name', 'Attribute Value', 'Locale'] + + private static final List HEADER_IDENTIFIER_FOR_PART = ['Part Number', 'Locale'] + + private final ExcelBuilderHelper excelBuilderHelper = new ExcelBuilderHelper() + + private final ExcelParserHelper excelParserHelper = new ExcelParserHelper() + + @SuppressWarnings(['CatchException']) + void triggerProcess(LocaleImportProcess localeImportProcess) { + try { + logStart(localeImportProcess) + List excelSheets = excelSheetsOfImportProcess(localeImportProcess) + if (excelSheets) { + HSSFWorkbook outputFile = new HSSFWorkbook() + saveSheetsDataAndGenerateExcel(excelSheets, outputFile, localeImportProcess) + outputFile.close() + logEnd(outputFile.sheets == [] ? 'Success' : 'Partial Success', 'Completed', localeImportProcess) + } else { logEnd('No Data', 'No Data Found', localeImportProcess) } + } catch (POIXMLException e) { + String message = (e.cause.class == InvalidFormatException) ? 'Corrupt: Invalid Format' : 'Invalid Excel' + logFailed(message, localeImportProcess, e) + } catch (FileNotFoundException e) { + logFailed('Internal Error: File not Found', localeImportProcess, e) + } catch (Exception e) { + logFailed('Internal Error: Please Contact helpDesk', localeImportProcess, e) + } + } + + private List excelSheetsOfImportProcess(LocaleImportProcess localeImportProcess) { + Workbook inputFile = excelParserHelper.workBook(filesLocation(localeImportProcess.id)) + excelParserHelper.collectSheetsFromExcel(inputFile, localeImportProcess.importType) + } + + private void saveSheetsDataAndGenerateExcel(List excelSheets, Workbook reportBook, LocaleImportProcess localeImportProcess) { + excelSheets.each { Map sheet -> + List errorData = saveSheet(sheet.data, sheet.sheetName, localeImportProcess.createdBy) + if (errorData != []) { + String reportFilePath = reportFilePath(localeImportProcess.id) + generateReportBySheet(errorData, sheet, reportBook, reportFilePath) + } + } + } + + private void generateReportBySheet(List errorData, Map sheet, Workbook reportBook, String reportFilePath) { + switch (sheet.sheetName) { + case 'CategoryAttributeValue': + excelBuilderHelper.reportBySheet(errorData, HEADER_IDENTIFIER_FOR_CATEGORY_ATTRIBUTE_VALUE, + reportBook, 'Report', reportFilePath) + excelBuilderHelper.reportBySheet(sheet.data, sheet.identifier, reportBook, sheet.sheetName, reportFilePath) + break + case 'Part': + excelBuilderHelper.reportBySheet(errorData, HEADER_IDENTIFIER_FOR_PART, + reportBook, 'Report', reportFilePath) + excelBuilderHelper.reportBySheet(sheet.data, sheet.identifier, reportBook, sheet.sheetName, reportFilePath) + break + case 'Product': + excelBuilderHelper.reportBySheet(errorData, sheet.identifier, reportBook, sheet.sheetName, reportFilePath) + break + case 'ProductFeature': + excelBuilderHelper.reportBySheet(errorData, sheet.identifier, reportBook, sheet.sheetName, reportFilePath) + break + } + } + + private String reportFilePath(Integer id) { "${filesLocation(id)}/report.xls" } + + private String filesLocation(Integer id) { "$dsLocaleImport$id" } + + List saveSheet(List data, String sheetName, String username) { + switch (sheetName) { + case 'ProductFeature': + bulkProductFeatureService.updateOrCreateProductFeatures(data, username) + break + case 'Product': + bulkProductService.updateOrCreateProducts(data, username) + break + case 'CategoryAttributeValue': + bulkCategoryAttributeValueService.updateOrCreateCategoryAttributeValue(data, username) + break + case 'Part': + bulkPartService.updateOrCreateParts(data, username) + break + } + } + + private void logStart(LocaleImportProcess localeImportProcess) { + setStatus(localeImportProcess, [status: 'Processing', startDate: new Date()]) + } + + private void logFailed(String message, LocaleImportProcess localeImportProcess, Exception e) { + logEnd('Failed', message, localeImportProcess, e) + } + + private void logEnd(String status, String message, LocaleImportProcess localeImportProcess, Exception e = null) { + if (e) { log.error e.message, e } + setStatus(localeImportProcess, [status: status, endDate: new Date(), message: message]) + } + + private void setStatus(LocaleImportProcess localeImportProcess, Map data) { + LocaleImportProcess.withTransaction { + localeImportProcess.properties = data + localeImportProcess.save(flush: true) + } + } +} Index: grails-app/services/com/lemans/ds/bulk/BulkProcessingHelperService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/bulk/BulkProcessingHelperService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/bulk/BulkProcessingHelperService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,20 @@ +package com.lemans.ds.bulk + +import com.lemans.services.LemansManager + +class BulkProcessingHelperService extends LemansManager { + + def messageSource + + protected static final Map LOCALE_COUNTRY_MAPPING = ['es': 'Spanish', 'de': 'German', 'fr': 'French', 'it': 'Italian'] + + + protected void collectIfErrors(Object object, List errors) { + object.errors.fieldErrors.each { + errors << "${it.field}: ${messageSource.getMessage(it, Locale.default)}" + } + object.errors.globalErrors.each { + errors << "${it.field}: ${messageSource.getMessage(it, Locale.default)}" + } + } +} Index: grails-app/services/com/lemans/ds/bulk/category/BulkCategoryAttributeValueService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/bulk/category/BulkCategoryAttributeValueService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/bulk/category/BulkCategoryAttributeValueService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,159 @@ +package com.lemans.ds.bulk.category + +import com.lemans.ds.bulk.BulkProcessingHelperService +import com.lemans.ds.category.CategoryAttributeValue +import com.lemans.ds.category.CategoryAttributeValueLocale +import org.apache.commons.lang.StringUtils +import org.apache.poi.ss.usermodel.Cell +import org.apache.poi.ss.usermodel.DataFormatter +import org.springframework.dao.DuplicateKeyException + +class BulkCategoryAttributeValueService extends BulkProcessingHelperService { + + List updateOrCreateCategoryAttributeValue(List data, String username) { + List categoryAttributeValueErrors = [] + data.each { Map eachRow -> + List categoryAttributeValueIds = (categoryAttributeValueIds(eachRow)).findAll { it != null } + List validDBIds = (categoryAttributeValueDBIds(categoryAttributeValueIds))*.id + Map transposeIdsWithNames = [:] + if (validDBIds) { + List validDbAttributeNames = getValidDBAttributeNames(validDBIds) + transposeIdsWithNames = [validDBIds, validDbAttributeNames].transpose().collectEntries { + [(it[0].toString()): it[1]] } + } + categoryAttributeValueIds.each { Integer id -> + if (id in validDBIds) { + updateOrCreateCategoryAttributeValueLocales(eachRow, username, id.intValue(), + transposeIdsWithNames."$id", categoryAttributeValueErrors) + } else { + Map errorsInMap = [:] + collectErrorsForReport(eachRow, 'Invalid Name for Invalid Id', + '', ["Invalid CategoryAttributeValueId: $id"], errorsInMap) + errorsInMap?.(categoryAttributeValueErrors << errorsInMap) + } + } + + } + categoryAttributeValueErrors + } + + @SuppressWarnings(['CatchException']) + private void updateOrCreateCategoryAttributeValueLocales(Map eachRow, String username, Integer id, + String attributeName, List categoryAttributeValueErrors) { + List explicitLocales = existingLocales(eachRow, attributeName) + if (explicitLocales) { + CategoryAttributeValueLocale.withTransaction { + List categoryAttributeValueLocaleObjects = findAllLocaleObjects(id, explicitLocales) + explicitLocales.each { String locale -> + List errors = [] + Map errorsInMap = [:] + try { + CategoryAttributeValueLocale categoryAttributeValueLocale = categoryAttributeValueLocaleObjects.find { + it.locale.equalsIgnoreCase(locale) } + Cell attributeValue = eachRow."$attributeName${LOCALE_COUNTRY_MAPPING[locale]}[${locale}]" + categoryAttributeValueLocale ? updateCategoryAttributeValueLocale(categoryAttributeValueLocale, + username, errors, attributeValue) : + addCategoryAttributeValueLocale(eachRow, locale, attributeName, username, errors) + if (errors != []) { + categoryAttributeValueErrors << collectErrorsForReport(eachRow, attributeName, locale, errors, errorsInMap) + } + } catch (DuplicateKeyException e) { + log.error(e.message) + categoryAttributeValueErrors << collectErrorsForReport(eachRow, attributeName, locale, + ['duplicate categoryAttributeValue Found'], errorsInMap) + } catch (Exception e) { + log.error(e.message) + categoryAttributeValueErrors << collectErrorsForReport(eachRow, attributeName, locale, + ['Exception occurred! If persists, contact HelpDesk'], errorsInMap) + } + } + } + } + } + + private void updateCategoryAttributeValueLocale(CategoryAttributeValueLocale categoryAttributeValueLocale, + String username, List errors, Cell attributeValue) { + DataFormatter dataFormatter = new DataFormatter() + categoryAttributeValueLocale.attributeValue = dataFormatter.formatCellValue(attributeValue) + categoryAttributeValueLocale.validate() + saveOrDiscardDomain(categoryAttributeValueLocale, username) + if (!categoryAttributeValueLocale.hasErrors()) { categoryAttributeValueLocale.save(flush: true) } + collectIfErrors(categoryAttributeValueLocale, errors) + } + + private void addCategoryAttributeValueLocale(Map eachRow, String locale, String attributeName, + String username, List errors) { + CategoryAttributeValueLocale categoryAttributeValueLocale = new CategoryAttributeValueLocale() + assignValues(categoryAttributeValueLocale, eachRow, attributeName, locale) + categoryAttributeValueLocale.validate() + saveOrDiscardDomain(categoryAttributeValueLocale, username) + if (!categoryAttributeValueLocale.hasErrors()) { categoryAttributeValueLocale.save(flush: true) } + collectIfErrors(categoryAttributeValueLocale, errors) + } + + private void assignValues(CategoryAttributeValueLocale categoryAttributeValueLocale, Map eachRow, String attributeName, + String locale) { + DataFormatter dataFormatter = new DataFormatter() + categoryAttributeValueLocale.categoryAttributeValueId = dataFormatter. + formatCellValue(eachRow."${attributeName}CategoryAttributeValueId").toInteger() + categoryAttributeValueLocale.attributeValue = dataFormatter. + formatCellValue(eachRow."$attributeName${LOCALE_COUNTRY_MAPPING[locale]}[${locale}]") + categoryAttributeValueLocale.locale = locale.toLowerCase() + } + + private List existingLocales(Map eachRow, String attributeName) { + List explicitLocales = eachRow.collect { String key, value -> + if (key.startsWith("$attributeName")) { + if (value.toString()) { + StringUtils.substringBetween(key, '[', ']') + } + } + } + explicitLocales.findAll { it != null } + } + + private List findAllLocaleObjects(Integer categoryAttributeValueId, List locales) { + CategoryAttributeValueLocale.findAllByCategoryAttributeValueIdAndLocaleInListAndDateDeletedIsNull(categoryAttributeValueId, locales) + } + + private List categoryAttributeValueDBIds(List categoryAttributeValueIds) { + categoryAttributeValueIds ? CategoryAttributeValue.findAllByIdInListAndDateDeletedIsNull(categoryAttributeValueIds) : [] + } + + private List getValidDBAttributeNames(List validDBIds) { + if (validDBIds) { + List attributeNamesWithSpaces = ((sql().rows('SELECT attributeName FROM vwCategoryAttributeValue WHERE ' + + 'categoryAttributeValueId IN (' + validDBIds.join(',') + ')'))*.attributeName).unique() + discardSpaces(attributeNamesWithSpaces) + } + } + + private List discardSpaces(List attributeNames) { + List noSpaces = [] + attributeNames.each { noSpaces << it.toString().replaceAll('\\s', '') } + noSpaces + } + + private List categoryAttributeValueIds(Map eachRow) { + eachRow.collect { String key, value -> + if (key.endsWith('CategoryAttributeValueId')) { + if (value.toString() != '') { + (value.toString() as Double).intValue() + } + } + } + } + + private void collectErrorsIntoMap(Map eachRow, String attributeName, String locale, List errors, Map errorsInMap) { + errorsInMap.put('Category Id', eachRow.categoryId) + errorsInMap.put('Attribute Name', attributeName) + errorsInMap.put('Attribute Value', eachRow."$attributeName" ? eachRow."$attributeName" : 'No Value') + errorsInMap.put('Locale', locale) + errorsInMap.put('report', errors) + } + + private Map collectErrorsForReport(Map eachRow, String attributeName, String locale, List errors, Map errorsInMap) { + collectErrorsIntoMap(eachRow, attributeName, locale, errors, errorsInMap) + if (errorsInMap) { errorsInMap } + } +} Index: grails-app/services/com/lemans/ds/bulk/part/BulkPartService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/bulk/part/BulkPartService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/bulk/part/BulkPartService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,109 @@ +package com.lemans.ds.bulk.part + +import com.lemans.ds.bulk.BulkProcessingHelperService +import com.lemans.ds.part.Part +import com.lemans.ds.part.PartLocale +import org.apache.commons.lang.StringUtils +import org.springframework.dao.DuplicateKeyException + +class BulkPartService extends BulkProcessingHelperService { + + List updateOrCreateParts(List data, String username) { + List partsWithErrors = [] + List partIDs = data*.partNumber + List validDBPartNumbers = (Part.findAllByIdInListAndDateDeletedIsNull(partIDs))*.id + data.each { Map eachRow -> + String partNumber = eachRow.partNumber + if (partNumber in validDBPartNumbers) { + updateOrCreatePartsLocales(eachRow, username, partNumber, partsWithErrors) + } else { + Map errorsInMap = [:] + collectErrorsForReport(eachRow, '', errorsInMap, ["Invalid Part Number : $partNumber"]) + errorsInMap ?. (partsWithErrors << errorsInMap) + } + } + partsWithErrors + } + + @SuppressWarnings(['CatchException']) + private void updateOrCreatePartsLocales(Map eachRow, String username, String partNumber, List partsWithErrors) { + List explicitLocales = findLocales(eachRow) + if (explicitLocales) { + PartLocale.withTransaction { + List partLocaleObjects = PartLocale.findAllByPartNumberAndLocaleInListAndDateDeletedIsNull(partNumber, explicitLocales) + explicitLocales.each { String locale -> + List errors = [] + Map errorsInMap = [:] + try { + PartLocale partLocale = partLocaleObjects.find { it.locale.equalsIgnoreCase(locale) } + partLocale ? updatePartLocale(eachRow, partLocale, username, locale, errors) : + addPartLocale(eachRow, username, locale, partNumber, errors) + if (errors != []) { + partsWithErrors << collectErrorsForReport(eachRow, locale, errorsInMap, errors) + } + } catch (DuplicateKeyException e) { + log.error(e.message) + partsWithErrors << collectErrorsForReport(eachRow, locale, errorsInMap, ['duplicate part found']) + } catch (Exception e) { + log.error(e.message) + partsWithErrors << collectErrorsForReport(eachRow, locale, errorsInMap, + ['Exception occurred! If persists, contact HelpDesk']) + } + } + } + } + partsWithErrors + } + + private Map collectErrorsForReport(Map eachRow, String locale, Map errorsInMap, List errors) { + collectErrorsIntoMap(eachRow, locale, errorsInMap, errors) + if (errorsInMap) { errorsInMap } + } + + private void updatePartLocale(Map eachRow, PartLocale partLocale, String username, String locale, List errors) { + assignValues(partLocale, eachRow, locale) + partLocale.validate() + saveOrDiscardDomain(partLocale, username) + if (!partLocale.hasErrors()) { partLocale.save(flush: true) } + collectIfErrors(partLocale, errors) + } + + private void addPartLocale(Map eachRow, String username, String locale, String partNumber, List errors) { + PartLocale partLocale = new PartLocale() + partLocale.locale = locale + partLocale.partNumber = partNumber + assignValues(partLocale, eachRow, locale) + partLocale.validate() + saveOrDiscardDomain(partLocale, username) + if (!partLocale.hasErrors()) { partLocale.save(flush: true) } + collectIfErrors(partLocale, errors) + } + + private void assignValues(PartLocale partLocale, Map eachRow, String locale) { + List assignableKeys = eachRow.collect { String key, value -> + if (key.endsWith("[$locale]")) { + if (value.toString()) { key.replace("${LOCALE_COUNTRY_MAPPING[locale]}[${locale}]", '') } + } + } + List validPartLocaleFields = assignableKeys.findAll { it != null }.unique() + validPartLocaleFields.each { String value -> + partLocale."$value" = eachRow."$value${LOCALE_COUNTRY_MAPPING[locale]}[${locale}]" + } + } + + private List findLocales(Map eachRow) { + List locales = eachRow.collect { String key, value -> + if (key.startsWith('marketingDescr') || key.startsWith('specialInstructions') + || key.startsWith('partSpecificText')) { + if (value.toString()) { StringUtils.substringBetween(key, '[', ']') } + } + } + locales.findAll { it != null }.unique() + } + + private void collectErrorsIntoMap(Map eachRow, String locale, Map errorsInMap, List errors) { + errorsInMap.put('Part Number', eachRow.partNumber) + errorsInMap.put('Locale', locale) + errorsInMap.put('report', errors) + } +} Index: grails-app/services/com/lemans/ds/bulk/product/BulkProductFeatureService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/bulk/product/BulkProductFeatureService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/bulk/product/BulkProductFeatureService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,102 @@ +package com.lemans.ds.bulk.product + +import com.lemans.ds.bulk.BulkProcessingHelperService +import com.lemans.ds.product.ProductFeatureLocale +import org.apache.commons.lang.StringUtils +import org.hibernate.StaleStateException +import org.springframework.dao.DuplicateKeyException + +class BulkProductFeatureService extends BulkProcessingHelperService { + + List updateOrCreateProductFeatures(List productFeatures, String username) { + List productFeaturesWithValidProductFeatureId = productFeaturesWithValidProductFeatureId(productFeatures) + List validDBProductFeatureIds = validProductFeatureIds(productFeaturesWithValidProductFeatureId*.ProductFeatureIdDummy) + List productFeaturesWithErrors = [] + productFeaturesWithValidProductFeatureId.each { Map productFeature -> + List errors = [] + Integer productFeatureId = productFeature.ProductFeatureIdDummy + if (validDBProductFeatureIds.contains(productFeatureId)) { + updateOrCreateProductFeatureLocale(productFeature, errors, username) + } else { errors << "Invalid productFeatureId: ${productFeatureId}" } + if (errors) { + productFeature.put('report', errors) + productFeature.remove('ProductFeatureIdDummy') + productFeaturesWithErrors << productFeature + } + } + productFeaturesWithErrors + } + + @SuppressWarnings(['CatchException']) + private void updateOrCreateProductFeatureLocale(Map values, List errors, String username) { + List existingLocales = getLocalesWithData(values) + if (existingLocales) { + ProductFeatureLocale.withTransaction { + try { + List localeObjects = findAllProductFeatureLocales(values.ProductFeatureIdDummy, existingLocales) + existingLocales.each { String locale -> + ProductFeatureLocale productFeatureLocale = localeObjects.find { it.locale.equalsIgnoreCase(locale) } + productFeatureLocale ? updateProductFeatureLocales(productFeatureLocale, locale, values, errors, username) + : addProductFeatureLocale(values, locale, errors, username) + } + } catch (DuplicateKeyException e) { + log.error e.message, e + errors << 'Duplicate productFeature Found' + } catch (StaleStateException e) { + log.error e.message, e + errors << 'Product FeatureId has already been updated, please try again' + } catch (Exception e) { + log.error e.message, e + errors << 'Exception occurred, please try again. If it persists, please let the development team know about it' + } + } + } + } + + private void updateProductFeatureLocales(ProductFeatureLocale productFeatureLocale, + String locale, Map values, List errors, String username) { + productFeatureLocale.featureText = values["FeatureText${LOCALE_COUNTRY_MAPPING[locale]}[${locale}]"] + productFeatureLocale.validate() + saveOrDiscardDomain(productFeatureLocale, username) + if (!productFeatureLocale.hasErrors()) { productFeatureLocale.save(flush: true) } + collectIfErrors(productFeatureLocale, errors) + } + + private void addProductFeatureLocale(Map values, String locale, List errors, String username) { + ProductFeatureLocale productFeatureLocale = new ProductFeatureLocale() + productFeatureLocale.productFeatureId = values.ProductFeatureIdDummy + productFeatureLocale.featureText = values["FeatureText${LOCALE_COUNTRY_MAPPING[locale]}[${locale}]"] + productFeatureLocale.locale = locale.toLowerCase() + productFeatureLocale.validate() + saveOrDiscardDomain(productFeatureLocale, username) + if (!productFeatureLocale.hasErrors()) { productFeatureLocale.save(flush: true) } + collectIfErrors(productFeatureLocale, errors) + } + + private List validProductFeatureIds(List productFeatureIds) { + sql().rows('SELECT productFeatureId FROM vwProductFeature WHERE productFeatureId IN (' + + productFeatureIds.join(',') + ')')?.productFeatureId + } + + private List findAllProductFeatureLocales(Integer productFeatureId, List locales) { + ProductFeatureLocale.findAllByProductFeatureIdAndLocaleInListAndDateDeletedIsNull(productFeatureId, locales) + } + + private List getLocalesWithData(Map data) { + List explicitLocales = data.collect { String key, value -> + if (key.startsWith('FeatureText')) { + if (value.toString()) { + StringUtils.substringBetween(key, '[', ']') + } + } + } + explicitLocales.findAll { it != null } + } + + private List productFeaturesWithValidProductFeatureId(List productFeatures) { + productFeatures.findAll { it.ProductFeatureId.toString() != '' }.collect { + it.ProductFeatureIdDummy = (it.ProductFeatureId?.toString() as Double)?.intValue() + it + } + } +} Index: grails-app/services/com/lemans/ds/bulk/product/BulkProductService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/bulk/product/BulkProductService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/bulk/product/BulkProductService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,113 @@ +package com.lemans.ds.bulk.product + +import com.lemans.ds.bulk.BulkProcessingHelperService +import com.lemans.ds.product.ProductLocale +import org.apache.commons.lang.StringUtils +import org.hibernate.StaleStateException +import org.springframework.dao.DuplicateKeyException + +class BulkProductService extends BulkProcessingHelperService { + + List updateOrCreateProducts(List products, String username) { + List productsWithValidProductId = productsWithValidProductId(products) + List validDBProductIds = validProductIds(productsWithValidProductId*.ProductIdDummy) + List productsWithErrors = [] + productsWithValidProductId.each { Map product -> + List errors = [] + Integer productId = product.ProductIdDummy + if (validDBProductIds.contains(productId)) { + updateOrCreateProductLocales(product, errors, username) + } else { errors << "Invalid productId: ${productId}" } + if (errors) { + product.put('report', errors) + product.remove('ProductIdDummy') + productsWithErrors << product + } + } + productsWithErrors + } + + @SuppressWarnings(['CatchException']) + private void updateOrCreateProductLocales(Map values, List errors, String username) { + List existingLocales = getLocalesWithData(values) + if (existingLocales) { + ProductLocale.withTransaction { + try { + List productLocaleObjects = findAllProductLocales(values.ProductIdDummy, existingLocales) + existingLocales.each { String locale -> + ProductLocale productLocale = productLocaleObjects.find { it.locale.equalsIgnoreCase(locale) } + productLocale ? updateProductLocale(productLocale, locale, values, errors, username) + : addProductLocale(values, locale, errors, username) + } + } catch (DuplicateKeyException e) { + log.error e.message, e + errors << 'Duplicate Product Found' + } catch (StaleStateException e) { + log.error e.message, e + errors << 'Product has been updated by different user, please try again' + } catch (Exception e) { + log.error e.message, e + errors << 'Exception occurred, please try again. If it persists, please let the development team know about it' + } + } + } + } + + private void updateProductLocale(ProductLocale productLocale, String locale, Map values, List errors, String username) { + assignValues(productLocale, values, locale) + productLocale.validate() + saveOrDiscardDomain(productLocale, username) + if (!productLocale.hasErrors()) { productLocale.save(flush: true) } + collectIfErrors(productLocale, errors) + } + + private void addProductLocale(Map values, String locale, List errors, String username) { + ProductLocale productLocale = new ProductLocale() + productLocale.locale = locale.toLowerCase() + productLocale.productId = values.ProductIdDummy + assignValues(productLocale, values, locale) + productLocale.validate() + saveOrDiscardDomain(productLocale, username) + if (!productLocale.hasErrors()) { productLocale.save(flush: true) } + collectIfErrors(productLocale, errors) + } + + private void assignValues(ProductLocale productLocale, Map values, String locale) { + List assignableKeys = values.collect { String key, value -> + if (key.endsWith("[$locale]")) { + if (value.toString()) { key.replace("${LOCALE_COUNTRY_MAPPING[locale]}[${locale}]", '') } + } + } + List validPartLocaleFields = assignableKeys.findAll { it != null }.unique() + validPartLocaleFields.each { String value -> + productLocale."$value" = values."$value${LOCALE_COUNTRY_MAPPING[locale]}[${locale}]" + } + } + + private List findAllProductLocales(Integer productId, List locales) { + ProductLocale.findAllByProductIdAndLocaleInListAndDateDeletedIsNull(productId, locales) + } + + private List getLocalesWithData(Map data) { + List explicitLocales = data.collect { String key, value -> + if (key.startsWith('productName') || key.startsWith('caption') + || key.startsWith('description')) { + if (value.toString()) { + StringUtils.substringBetween(key, '[', ']') + } + } + } + explicitLocales.findAll { it != null }.unique() + } + + private List validProductIds(List productIds) { + sql().rows('SELECT productId FROM vwProduct WHERE productId IN (' + productIds.join(',') + ')')?.productId + } + + private List productsWithValidProductId(List products) { + products.findAll { it.ProductId.toString() != '' }.collect { + it.ProductIdDummy = (it.ProductId?.toString() as Double)?.intValue() + it + } + } +} Index: grails-app/services/com/lemans/ds/category/CategoryAttributeManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/category/CategoryAttributeManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/category/CategoryAttributeManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,186 @@ +package com.lemans.ds.category + +import com.lemans.FeatureToggles +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +@SuppressWarnings('CouldBeElvis') +class CategoryAttributeManagerService extends LemansManager { + + def categoryAttributeService + def categoryService + + private static final String SEQUENCE_SQL = + '''EXEC dbo.spMoveCategoryAttribute + @sourceId = :id, + @targetId = :targetId, + @sourceVersion = :sourceVersion, + @targetVersion = :targetVersion, + @position = :position, + @appSecurityUser = :appSecurityUser + ''' + + private static final String CATEGORY_ATTRIBUTE_MULTIPLE_VALUE = + 'SELECT dbo.fnCheckCategoryAttributeMultipleValue (?)' + + private static final int FORCED_OPT_LOCKING_VERSION = -1 + + /** + * Creates a CategoryAttribute. + * + * @param values + * @param username + * + * @return CategoryAttribute which may contain errors or be null + */ + CategoryAttribute createCategoryAttribute(Map values, String username) { + CategoryAttribute categoryAttribute = new CategoryAttribute(values) + categoryAttribute.validate() + Map criteria = [catalogInstanceId: values.catalogInstanceId, id: values.categoryId] + Map results = categoryService.findCategoryById(criteria) + if (!results || results.isEmpty()) { + categoryAttribute.errors.reject('invalid', 'Catalog or Category not found') + } + saveOrDiscardDomain(categoryAttribute, username) + } + + /** + * Updates a CategoryAttribute. + * + * @param values - must contain a version entry + * @param criteria - catalogInstanceId, categoryId and attributeNameId + * @param username + * @return CategoryAttribute which may contain errors or be null + */ + CategoryAttribute updateCategoryAttribute(Map values, Map criteria, String username) { + CategoryAttribute categoryAttribute = findCategoryAttribute(criteria) + if (categoryAttribute) { + applyValuesToDomain(values, categoryAttribute) + if (FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE) { + checkForStaleObject(categoryAttribute, 'CategoryAttribute', values.version ?: FORCED_OPT_LOCKING_VERSION) + } + validateAllowMultipleValue(categoryAttribute) + saveOrDiscardDomain(categoryAttribute, username) + } + categoryAttribute + } + + /** + * Updates a CategoryAttribute. + * + * @param values - must contain a version entry + * @param criteria - catalogInstanceId, categoryId and attributeNameId + * @param username + * + * @return CategoryAttribute which may contain errors or be null + */ + CategoryAttributeLocale updateCategoryAttributeWithLocale(Map values, Map criteria, String username) { + Map data = categoryAttributeService.findCategoryAttributeLocaleByCompositKey(criteria) + CategoryAttributeLocale categoryAttributeLocale + if (data) { + categoryAttributeLocale = CategoryAttributeLocale.findByCategoryAttributeIdAndLocale(data.categoryAttributeId, values.locale) + if (!categoryAttributeLocale) { + categoryAttributeLocale = new CategoryAttributeLocale([categoryAttributeId: data.categoryAttributeId]) + } + } else { + return + } + applyValuesToDomain(values, categoryAttributeLocale) + categoryAttributeLocale.validate() + saveOrDiscardDomain(categoryAttributeLocale, username) + } + + /** + * Soft deletes a CategoryAttribute. + * + * @param values - must contain a version entry + * @param criteria - catalogInstanceId, categoryId and attributeNameId + * @param username + * @param version - the client's current version + * + * @return CategoryAttribute which may contain errors or be null + */ + CategoryAttribute deleteCategoryAttribute(Map criteria, String username, Integer version) { + CategoryAttribute categoryAttribute = findCategoryAttribute(criteria) + if (categoryAttribute) { + if (FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE) { + checkForStaleObject(categoryAttribute, 'CategoryAttribute', version ?: FORCED_OPT_LOCKING_VERSION) + } + if (partAttributesOfCategoryExists(categoryAttribute)) { + categoryAttribute.errors.reject('categoryAttribute.partsWithAttributeName.exists', 'Parts with attributeName exists') + } + softDelete(categoryAttribute, username) + } + categoryAttribute + } + + private boolean partAttributesOfCategoryExists(CategoryAttribute categoryAttribute) { + sql().rows('''SELECT * FROM vwPartAttribute WHERE attributeNameId = :attributeNameId AND +partNumber IN (SELECT partNumber FROM vwPart WHERE categoryId = :categoryId)''', + [categoryId: categoryAttribute.categoryId, attributeNameId: categoryAttribute.attributeNameId]) + } + + private CategoryAttribute findCategoryAttribute(Map criteria) { + Map results = categoryAttributeService.findCategoryAttributeByCompositKey(criteria) + CategoryAttribute categoryAttribute + if (results) { + categoryAttribute = findDomainObjectById(CategoryAttribute, results.categoryAttributeId) + } + categoryAttribute + } + + /** + * Sequence CategoryAttribute + * + * @param values - targetAttributeNameId, attributeNameId(source), position, catalogInstanceId, categoryId + * @param username + * @return sourceCategoryAttribute + */ + CategoryAttribute moveCategoryAttribute(Map values, String username) { + CategoryAttribute sourceCategoryAttribute = findCategoryAttribute(values) + values.attributeNameId = values.targetId + CategoryAttribute targetCategoryAttribute = findCategoryAttribute(values) + if (sourceCategoryAttribute && targetCategoryAttribute) { + if (FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE) { + checkForStaleObject(CategoryAttribute, 'CategoryAttribute', values.sourceVersion ?: FORCED_OPT_LOCKING_VERSION) + } + values.id = sourceCategoryAttribute.id + values.targetId = targetCategoryAttribute.id + sequence(values, username) + sourceCategoryAttribute + } + } + + /** + * Sequences a CategoryAttribute + * @param categoryAttribute + */ + void sequence(Map values, String username) { + sql().execute(values + [appSecurityUser: username], SEQUENCE_SQL) + } + + /** + * validate allowMultipleValue only if it is modified and false + */ + void validateAllowMultipleValue(CategoryAttribute categoryAttribute) { + if (!categoryAttribute.allowMultipleValues && categoryAttribute.isDirty('allowMultipleValues')) { + if (hasMultipleValueAssociation(categoryAttribute.id)) { + categoryAttribute.errors.rejectValue('allowMultipleValues', 'categoryAttribute.allowMultipleValues.invalid') + } + } + } + + /** + * return true if CategoryAttribute has multiple values assigned to any of the parts assigned to a category + * @param categoryAttributeId + */ + boolean hasMultipleValueAssociation(Integer categoryAttributeId) { + boolean flag = false + if (categoryAttributeId) { + Map results = sql().rows(CATEGORY_ATTRIBUTE_MULTIPLE_VALUE, categoryAttributeId)[0] + flag = results[(0)] + } + flag + } +} Index: grails-app/services/com/lemans/ds/category/CategoryAttributeService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/category/CategoryAttributeService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/category/CategoryAttributeService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,70 @@ +package com.lemans.ds.category + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional(readOnly = true) +class CategoryAttributeService extends LemansService { + + def categoryService + + + /** + * Find a categoryAttribute by categoryAttributeId. + * + * @param criteria containing categoryAttributeId + * + * @return categoryAttribute + */ + Map findCategoryAttributeById(Map criteria) { + List clauses = ['categoryAttributeId = :categoryAttributeId'] + dqx(criteria).executeOneFromLocale('dbo.vwCategoryAttribute', clauses)?.results[0] + } + + /** + * Find a CategoryAttribute by catalogId, categoryId and attributeId. + * + * @param criteria containing catalogInstanceId ,categoryId and attributeId + * + * @return CategoryAttribute + */ + Map findCategoryAttributeByCompositKey(Map criteria) { + List clauses = ['catalogInstanceId = :catalogInstanceId', 'categoryId = :categoryId', 'attributeNameId = :attributeNameId'] + dqx(criteria).executeOneFromLocale('dbo.vwCategoryAttribute', clauses)?.results[0] + } + + /** + * Find a CategoryAttribute by catalogId, categoryId and attributeId. + * + * @param criteria containing catalogInstanceId ,categoryId and attributeId + * + * @return CategoryAttribute + */ + Map findCategoryAttributeLocaleByCompositKey(Map criteria) { + List clauses = [ + 'catalogInstanceId = :catalogInstanceId', + 'categoryId = :categoryId', + 'attributeNameId = :attributeNameId' + ] + dqx(criteria).executeOneFrom('dbo.vwCategoryAttributeLocale', clauses)?.results[0] + } + + /** + * Find a CategoryAttributes by catalogId and categoryId. + * + * @param criteria containing catalogInstanceId and categoryId + * + * @return CategoryAttributes + */ + Map findCategoryAttributeByCategory(Map criteria) { + List clauses = ['catalogInstanceId = :catalogInstanceId', 'categoryId = :categoryId'] + criteria.sorting = criteria.sorting ?: 'sequence+ASC' + Map results = dqx(criteria).executeFromLocale('dbo.vwCategoryAttribute', clauses) + if (results.results.isEmpty()) { //check if catgoryInstanceId and category exist. + Map categoryCriteria = [catalogInstanceId: criteria.catalogInstanceId, id: criteria.categoryId] + Map categoryMap = categoryService.findCategoryById(categoryCriteria) + return categoryMap ? results : categoryMap + } + results + } +} Index: grails-app/services/com/lemans/ds/category/CategoryAttributeValueManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/category/CategoryAttributeValueManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/category/CategoryAttributeValueManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,147 @@ +package com.lemans.ds.category + +import com.lemans.FeatureToggles +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class CategoryAttributeValueManagerService extends LemansManager { + + def categoryAttributeValueService + def categoryAttributeService + + private static final int FORCED_OPT_LOCKING_VERSION = -1 + + private static final String SEQUENCE_SQL = + '''EXEC dbo.spMoveCategoryAttributeValue + @sourceId = :sourceId, + @targetId = :targetId, + @sourceVersion = :sourceVersion, + @targetVersion = :targetVersion, + @position = :position, + @appSecurityUser = :appSecurityUser + ''' + + /** + * Creates a CategoryAttributeValue. + * + * @param values + * @param username + * + * @return CategoryAttributeValue which may contain errors or be null + */ + CategoryAttributeValue createCategoryAttributeValue(Map values, String username) { + CategoryAttributeValue categoryAttributeValue = new CategoryAttributeValue(values) + Map results = categoryAttributeService.findCategoryAttributeByCompositKey(values) + if (!results || results.isEmpty()) { + categoryAttributeValue.errors.reject('invalid', 'Catalog -> Category -> attributeName relation not found') + } else { + categoryAttributeValue.categoryAttributeId = results.categoryAttributeId + categoryAttributeValue.validate() + } + saveOrDiscardDomain(categoryAttributeValue, username) + } + + /** + * Updates a CategoryAttributeValue. + * + * @param values - must contain a version entry + * @param criteria - catalogInstanceId, categoryId and attributeNameId + * @param username + * @return CategoryAttribute which may contain errors or be null + */ + CategoryAttributeValue updateCategoryAttributeValue(Map values, String username) { + CategoryAttributeValue categoryAttributeValue = findDomainObjectById(CategoryAttributeValue, values.attributeValueId) + if (categoryAttributeValue) { + applyValuesToDomain(values, categoryAttributeValue) + categoryAttributeValue.validate() + saveOrDiscardDomain(categoryAttributeValue, username) + } + categoryAttributeValue + } + + /** + * Updates a CategoryAttributeValue. + * + * @param values - must contain a version entry + * @param criteria - catalogInstanceId, categoryId and attributeNameId + * @param username + * @return CategoryAttribute which may contain errors or be null + */ + CategoryAttributeValueLocale updateCategoryAttributeValueWithLocale(Map values, String username) { + Map data = categoryAttributeValueService.findCategoryAttributeValueWithLocaleByByCompositKey(values) + if (data) { + CategoryAttributeValueLocale categoryAttributeValueLocale = + categroyAttributeLocale(data, values) ?: new CategoryAttributeValueLocale([categoryAttributeValueId: data.categoryAttributeValueId]) + applyValuesToDomain(values, categoryAttributeValueLocale) + categoryAttributeValueLocale.validate() + saveOrDiscardDomain(categoryAttributeValueLocale, username) + } + } + + private CategoryAttributeValueLocale categroyAttributeLocale(Map data, Map values) { + CategoryAttributeValueLocale.findByCategoryAttributeValueIdAndLocale(data.categoryAttributeValueId, values.locale) + } + + /** + * Soft deletes a CategoryAttributeValue. + * + * @param values - must contain a version entry + * @param criteria - catalogInstanceId, CategoryId, AttributeNameId and attributeValueId + * @param username + * @param version - the client's current version + * + * @return CategoryAttributeValue which may contain errors or be null + */ + CategoryAttributeValue deleteCategoryAttributeValue(Map criteria, String username, Integer version) { + CategoryAttributeValue categoryAttributeValue = findCategoryAttributeValue(criteria) + if (categoryAttributeValue) { + if (FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE) { + checkForStaleObject(categoryAttributeValue, 'CategoryAttributeValue', version ?: FORCED_OPT_LOCKING_VERSION) + } + softDelete(categoryAttributeValue, username) + } + categoryAttributeValue + } + + /** + * Finds categoryAttributeValue by catalogInstanceId, CategoryId, AttributeNameId and attributeValueId + * @param criteria has catalogInstanceId, CategoryId, AttributeNameId and attributeValueId + * @return categoryAttributeValue + */ + private CategoryAttributeValue findCategoryAttributeValue(Map criteria) { + Map results = categoryAttributeValueService.findCategoryAttributeValueByCompositKey(criteria) + CategoryAttributeValue categoryAttributeValue + if (results) { + categoryAttributeValue = CategoryAttributeValue.findByIdAndDateDeletedIsNull(results.categoryAttributeValueId) + } + categoryAttributeValue + } + + /** + * Sequence CategoryAttributeValue + * + * @param values - targetAttributeValueId, attributeValueId(source), position, catalogInstanceId, categoryId, attributeNameId + * @param username + * @return sourceCategoryAttributeValue + */ + Map moveCategoryAttributeValue(Map values, String username) { + Map sourceCategoryAttributeValue = categoryAttributeValueService.findCategoryAttributeValueByCompositKey(values) + values.attributeValueId = values.targetId + Map targetCategoryAttributeValue = categoryAttributeValueService.findCategoryAttributeValueByCompositKey(values) + if (sourceCategoryAttributeValue && targetCategoryAttributeValue) { + values.sourceId = sourceCategoryAttributeValue.categoryAttributeValueId + values.targetId = targetCategoryAttributeValue.categoryAttributeValueId + sequence(values, username) + sourceCategoryAttributeValue + } + } + + /** + * Sequences a CategoryAttributeValue + * @param categoryAttributeValue + */ + void sequence(Map values, String username) { + sql().execute(values + [appSecurityUser: username], SEQUENCE_SQL) + } +} Index: grails-app/services/com/lemans/ds/category/CategoryAttributeValueService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/category/CategoryAttributeValueService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/category/CategoryAttributeValueService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,70 @@ +package com.lemans.ds.category + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional +class CategoryAttributeValueService extends LemansService { + + def categoryAttributeService + + + /** + * Find a categoryAttributeValue by categoryAttributeValueId. + * + * @param criteria containing categoryAttributeValueId + * + * @return categoryAttributeValue + */ + Map findCategoryAttributeValueById(Map criteria) { + List clauses = ['categoryAttributeValueId = :categoryAttributeValueId'] + dqx(criteria).executeOneFromLocale('dbo.vwCategoryAttributeValue', clauses)?.results[0] + } + + /** + * Find a CategoryAttributeValue by catalogId, categoryId, attributeNameId and attributeValueId. + * + * @param criteria containing catalogInstanceId ,categoryId, attributeNameId and attributeValueId + * + * @return CategoryAttributeValue + */ + Map findCategoryAttributeValueByCompositKey(Map criteria) { + List clauses = ['catalogInstanceId = :catalogInstanceId', 'categoryId = :categoryId', 'attributeNameId = :attributeNameId', + 'attributeValueId = :attributeValueId'] + dqx(criteria).executeOneFromLocale('dbo.vwCategoryAttributeValue', clauses)?.results[0] + } + + /** + * Find a attribute by attributeValueId. + * + * @param criteria containing attributeValueId + * + * @return attribute + */ + def findCategoryAttributeValueWithLocaleByByCompositKey(Map criteria) { + List clauses = [ + 'categoryId = :categoryId', + 'attributeNameId = :attributeNameId', + 'attributeValueId = :attributeValueId', + 'locale = :locale' + ] + dqx(criteria).executeOneFrom('dbo.vwCategoryAttributeValueLocale', clauses)?.results[0] + } + + /** + * Find a CategoryAttributeValues by catalogId, categoryId and attributeNameId. + * + * @param criteria containing catalogInstanceId, categoryId and attributeNameId + * + * @return CategoryAttributeValues + */ + Map findCategoryAttributeValueByCriteria(Map criteria) { + List clauses = ['catalogInstanceId = :catalogInstanceId', 'categoryId = :categoryId', 'attributeNameId = :attributeNameId'] + Map results = dqx(criteria).executeFromLocale('dbo.vwCategoryAttributeValue', clauses) + if (results.results.isEmpty()) { //check if catgoryInstanceId and category exist. + Map categoryAttribute = categoryAttributeService.findCategoryAttributeByCompositKey(criteria) + return categoryAttribute ? results : categoryAttribute + } + results + } +} Index: grails-app/services/com/lemans/ds/category/CategoryManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/category/CategoryManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/category/CategoryManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,188 @@ +package com.lemans.ds.category + +import com.lemans.FeatureToggles +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class CategoryManagerService extends LemansManager { + + def categoryService + + private static final String SEQUENCE_SQL = 'EXEC dbo.spCategoryUpdateSequence @categoryId = ?' + + private static final int FORCED_OPT_LOCKING_VERSION = -1 + + /** + * Creates a Category. + * + * @param values + * @param username + * + * @return Category which may contain errors or be null + */ + Category createCategory(Map values, String username) { + Category category = new Category(values) + if (category.parentCategoryId) { + Map parent = categoryService.findCategoryTreeInfoById( + catalogInstanceId: category.catalogInstanceId, id: category.parentCategoryId + ) + if (!parent) { return null } + + if (parent.level >= Category.MAX_DEPTH) { rejectDepth(category) } + } + if (!category.hasErrors()) { + category.sequence = Category.MAX_DIRECT_CHILDREN + saveOrDiscardDomain(category, username) + } + category + } + + /** + * Updates a Category. + * + * @param values - must contain a version entry + * @param catalogInstanceId + * @param id + * @param username + * + * @return Category which may contain errors or be null + */ + Category updateCategory(Map values, Integer catalogInstanceId, Integer id, String username) { + Category category = findCategory(catalogInstanceId, id) + if (category) { + applyValuesToDomain(values, category) + if (FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE) { + checkForStaleObject(category, 'Category', values.version ?: FORCED_OPT_LOCKING_VERSION) + } + saveOrDiscardDomain(category, username) + } + category + } + + /** + * Moves a Category relative to another Category for the same Category. + * + * @param values - must contain the shared catalogInstanceId, + * an id (source), A targetId (destination), and A position (BEFORE, AFTER, or INSIDE) + * @param username + * + * @return sourceCategory + */ + Category moveCategory(Map values, String username) { + Integer catalogInstanceId = values.catalogInstanceId + Category category = findCategory(catalogInstanceId, values.id) + if (category) { + if (FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE) { + checkForStaleObject(category, 'Category', values.version ?: FORCED_OPT_LOCKING_VERSION) + } + Map source = categoryService.findCategoryTreeInfoById(catalogInstanceId: catalogInstanceId, id: values.id) + Map target = categoryService.findCategoryTreeInfoById(catalogInstanceId: catalogInstanceId, id: values.targetId) + if (!source || !target) { return null } + + checkTreeConstraints(category, source, target) + if (values.position == 'INSIDE') { + if (!category.hasErrors()) { + category.parentCategoryId = target.categoryId + category.sequence = 1 + } + } + else { + if (!category.hasErrors()) { + category.parentCategoryId = target.p1categoryId + category.sequence = values.position == 'AFTER' ? target.sequence + 1 : target.sequence + } + } + saveOrDiscardDomain(category, username) + category + } else { null } + } + + private void checkTreeConstraints(Category category, source, target) { + if (target.level + source.depth > Category.MAX_DEPTH) { rejectDepth(category) } + Set parentIds = [target.p1categoryId, target.p2categoryId, target.p3categoryId] + if (category.id in parentIds) { rejectCycle(category) } + } + + /** + * Soft deletes a Category. + * + * @param catalogInstanceId + * @param id + * @param username + * @param version - the client's current version + * + * @return Category which may contain errors or be null + */ + Category deleteCategory(Integer catalogInstanceId, Integer id, String username, Integer version) { + Category category = findCategory(catalogInstanceId, id) + if (category) { + if (FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE) { + checkForStaleObject(category, 'Category', version ?: FORCED_OPT_LOCKING_VERSION) + } + if (children(category)) { + category.errors.reject('undeletable', ['Category'] as Object[], 'Category may not be deleted') + } else { + softDelete(category, username) + } + } + category + } + + /** + * Finds all non-deleted categories for a catalog. + * + * @param catalogInstanceId + * + * @return List of categories + */ + List findCategoriesForCatalog(Integer catalogInstanceId) { + Category.findAllByCatalogInstanceIdAndDateDeletedIsNull(catalogInstanceId) + } + + /** + * Sequences a Category to respect the parentCategoryId and sequence properties. + * + * @param category + */ + void sequence(Category category) { + if (!category.hasErrors()) { + sql().execute(SEQUENCE_SQL, [category.id]) + } + } + + private rejectDepth(Category category) { + category.errors.reject('maxDepth.exceeded', ['Category'] as Object[], "Category depth of $Category.MAX_DEPTH may not be exceeded") + } + + private rejectCycle(Category category) { + category.errors.reject('cyclic', ['Category'] as Object[], 'Category may not be moved to create a cycle') + } + + private List children(Category category) { + Category.findAllByParentCategoryIdAndDateDeletedIsNull(category.id) + } + + private Category findCategory(Integer catalogInstanceId, Integer categoryId) { + Category.findByCatalogInstanceIdAndIdAndDateDeletedIsNull(catalogInstanceId, categoryId) + } + + CategoryLocale updateCategoryLocale(Map values, Integer catalogInstanceId, Integer categoryId, String username) { + CategoryLocale categoryLocale = findCategoryLocale(categoryId, values.locale) + if (!categoryLocale) { + Category category = findCategory(catalogInstanceId, categoryId) + if (category) { + categoryLocale = new CategoryLocale([categoryId: category.id]) + } else { + return + } + } + applyValuesToDomain(values, categoryLocale) + categoryLocale.validate() + saveOrDiscardDomain(categoryLocale, username) + } + + private CategoryLocale findCategoryLocale(Integer categoryId, String locale) { + CategoryLocale.findByCategoryIdAndLocaleAndDateDeletedIsNull(categoryId, locale) + } +} Index: grails-app/services/com/lemans/ds/category/CategoryService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/category/CategoryService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/category/CategoryService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,82 @@ +package com.lemans.ds.category + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional(readOnly = true) +class CategoryService extends LemansService { + + static final List TREE_COLUMNS = ['categoryName', 'categoryId', 'parentCategoryId', 'sequence', 'version'].asImmutable() + + static final List DROPDOWN_COLUMNS = ['categoryId', 'qualifiedCategoryName'].asImmutable() + + private static final Map COLUMN_SETS = [_tree: TREE_COLUMNS, _dropdown: DROPDOWN_COLUMNS].asImmutable() + + private static final String CATEGORY_DETAILS_SQL = 'SELECT dbo.fnGetCategoryDetailsJSON(?)' + + private static final String CATEGORY_DETAILS_LOCALE_SQL = 'SELECT dbo.fnGetCategoryDetailsJSONLocale(?,?)' + + + /** + * Find Categories for a Catalog. + * + * @param criteria containing catalogInstanceId + * + * @return Categories data + */ + List findCategories(Map criteria) { + List clauses = ['catalogInstanceId = :catalogInstanceId'] + if (criteria.categoryId) { + List categoryIds = criteria.categoryId.split(',') + clauses << "categoryId IN (${categoryIds.join(',')})" + } + dqx(criteria).executeFrom('dbo.vwCategory', clauses, COLUMN_SETS).results + } + + /** + * Find a Category by id. + * + * @param criteria containing catalogInstanceId and id (categoryId) + * + * @return Category data + */ + Map findCategoryById(Map criteria) { + List clauses = ['catalogInstanceId = :catalogInstanceId', 'categoryId = :id'] + dqx(criteria).executeOneFromLocale('dbo.vwCategory', clauses)?.results[0] + } + + boolean categoryExists(Map criteria) { + sql().firstRow(criteria, 'SELECT * FROM dbo.vwCategory WHERE catalogInstanceId = :catalogInstanceId AND categoryId = :categoryId') + } + + /** + * Find Category hierarchy information by id. + * + * @param criteria containing catalogInstanceId and categoryId + * + * @return Category data + */ + Map findCategoryTreeInfoById(Map criteria) { + List clauses = ['catalogInstanceId = :catalogInstanceId', 'categoryId = :id'] + dqx(criteria).executeOneFrom('dbo.vwCategoryLevel', clauses)?.results[0] + } + + /** + * Find Category details information by id. + * + * @param criteria containing catalogInstanceId and categoryId + * + * @return Category details with attribute and attributeValue + */ + String categoryDetails(Map criteria) { + if (findCategoryById(criteria)) { + if (criteria.locale == Locale.default.toString()) { + queryForXmlClob(CATEGORY_DETAILS_SQL, [criteria.id]) + } else { + queryForXmlClob(CATEGORY_DETAILS_LOCALE_SQL, [criteria.id, criteria.locale]) + } + } else { + null + } + } +} Index: grails-app/services/com/lemans/ds/category/CategorySubComCodeManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/category/CategorySubComCodeManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/category/CategorySubComCodeManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,47 @@ +package com.lemans.ds.category + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class CategorySubComCodeManagerService extends LemansManager { + + def categoryService + + /** + * Adds a SubComCode mapping to a Category. + * + * @param values Map containing catalogInstanceId, categoryId, and subComCodeId + * @param username + * + * @return CategorySubComCode mapping which may be null or have errors + */ + CategorySubComCode addSubComCode(Map values, String username) { + Integer categoryId = values.categoryId + Map category = categoryService.findCategoryById([catalogInstanceId: values.catalogInstanceId, id: categoryId]) + CategorySubComCode categorySubComCode = new CategorySubComCode(categoryId: categoryId, subComCodeId: values.subComCodeId) + if (!category) { + categorySubComCode.errors.reject('invalid', 'Catalog or Category not found') + } + saveOrDiscardDomain(categorySubComCode, username) + } + + /** + * Removes a SubComCode mapping from a Category. + * + * @param values Map containing catalogInstanceId and categoryId + * @param subComCodeId + * @param username + * + * @return CategorySubComCode mapping which may be null or have errors + */ + CategorySubComCode removeSubComCode(Map values, Integer subComCodeId, String username) { + Map category = categoryService.findCategoryById([catalogInstanceId: values.catalogInstanceId, id: values.categoryId]) + CategorySubComCode subComCode + if (category) { + subComCode = CategorySubComCode.findByCategoryIdAndSubComCodeIdAndDateDeletedIsNull(category.categoryId, subComCodeId) + if (subComCode) { softDelete(subComCode, username) } + } + subComCode + } +} Index: grails-app/services/com/lemans/ds/category/CategorySubComCodeService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/category/CategorySubComCodeService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/category/CategorySubComCodeService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,33 @@ +package com.lemans.ds.category + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional(readOnly = true) +class CategorySubComCodeService extends LemansService { + + /** + * Finds a CategorySubComCode by categoryId and subComCodeId. + * + * @param criteria Map containing catalogInstanceId, categoryId, and subComCodeId + * + * @return Map containing CategorySubComCode data + */ + Map findCategorySubComCode(Map criteria) { + List clauses = categoryClauses() + 'subComCodeId = :subComCodeId' + dqx(criteria).executeOneFrom('dbo.vwCategorySubComCode', clauses)?.results[0] + } + + /** + * Finds all CategorySubComCodes for a Category. + * + * @param criteria Map containing catalogInstanceId and categoryId + * + * @return List containing CategorySubComCode data + */ + List findCategorySubComCodes(Map criteria) { + dqx(criteria).executeFrom('dbo.vwCategorySubComCode', categoryClauses()).results + } + + private List categoryClauses() { ['catalogInstanceId = :catalogInstanceId', 'categoryId = :categoryId'] } +} Index: grails-app/services/com/lemans/ds/explosion/ExplosionDiagramManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/explosion/ExplosionDiagramManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/explosion/ExplosionDiagramManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,56 @@ +package com.lemans.ds.explosion + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class ExplosionDiagramManagerService extends LemansManager { + + /** + * Add an explosion diagram + * @param input + * @param username + * @return Explosion diagram object + */ + ExplosionDiagram addExplosionDiagram(Map input, String username) { + ExplosionDiagram explosionDiagram = new ExplosionDiagram() + applyValuesToDomain(input, explosionDiagram) + explosionDiagram.validate() + saveOrDiscardDomain(explosionDiagram, username) + } + + /** + * Update an explosion diagram + * @param input + * @param diagramId + * @param username + * @return Explosion diagram object + */ + ExplosionDiagram updateExplosionDiagram(Map input, Integer diagramId, String username) { + ExplosionDiagram explosionDiagram = findExplosionDiagram(diagramId) + if (explosionDiagram) { + applyValuesToDomain(input, explosionDiagram) + explosionDiagram.validate() + saveOrDiscardDomain(explosionDiagram, username) + } + explosionDiagram + } + + /** + * Delete an explosion diagram + * @param diagramId + * @param username + * @return soft deleted explosion diagram object + */ + ExplosionDiagram deleteExplosionDiagram(Integer diagramId, String username) { + ExplosionDiagram explosionDiagram = findExplosionDiagram(diagramId) + if (explosionDiagram) { + softDelete(explosionDiagram, username) + } + explosionDiagram + } + + private ExplosionDiagram findExplosionDiagram(Integer diagramId) { + ExplosionDiagram.findByIdAndDateDeletedIsNull(diagramId) + } +} Index: grails-app/services/com/lemans/ds/explosion/ExplosionDiagramService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/explosion/ExplosionDiagramService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/explosion/ExplosionDiagramService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,44 @@ +package com.lemans.ds.explosion + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional +class ExplosionDiagramService extends LemansService { + + /** + * Find all explosion diagrams + * @param criteria + * @return map of explosionDiagrams + */ + Map findExplosionDiagrams(Map criteria) { + Map data = dqx(criteria).executeFrom('vwExplosionDiagram') + if (data) { [results: transformClobToText(data.results), totalRecords: data.totalRecords] } + } + + /** + * find an explosion diagram + * @param explosionDiagramId + * @return map of explosion diagram + */ + Map findExplosionDiagram(Integer explosionDiagramId) { + Map data = dqx([explosionDiagramId: explosionDiagramId]).executeOneFrom( + 'vwExplosionDiagram', ['explosionDiagramId = :explosionDiagramId'])?.results[0] + if (data) { transformClobToText(data) } + } + + private Map transformClobToText(Map data) { + data.collectEntries { k, v -> + if (v?.class?.simpleName == 'ClobImpl') { [k, v.characterStream.text] } + else { [k, v] } + } + } + + private List transformClobToText(List data) { + List results = [] + data.each { Map it -> + results << transformClobToText(it) + } + results + } +} Index: grails-app/services/com/lemans/ds/fitment/PartFitmentManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/fitment/PartFitmentManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/fitment/PartFitmentManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,128 @@ +package com.lemans.ds.fitment + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +import java.sql.Timestamp +import java.util.stream.Collectors + +/** + * Created by vramisetti on 6/12/2017. + */ +@Transactional +class PartFitmentManagerService extends LemansManager { + + def partService + + def partFitmentService + + def modelYearService + + /** + * Create part fitments + * + * @param values + * @param username + * + * @return Map containing errors + */ + Map createFitments(Map values, String username) { + List errors = [] + String partNumber = values.partNumber + List modelYearIds = ((List) values.modelYearIds).parallelStream().distinct().collect(Collectors.toList()) + List validModelYears = modelYearService.findValidModelYearIds(modelYearIds)?.modelYearId + List invalidModelYearIds = modelYearIds.parallelStream().filter { s -> !validModelYears.contains(s) }.collect(Collectors.toList()) + if (invalidModelYearIds) { errors << "Invalid model year's found $invalidModelYearIds" } + if (!partService.partWithPartNumberExists(partNumber)) { errors << 'Invalid part' } + if (!errors) { + List modelYearIdsToBeCreated = modelIdsToBeCreated(partNumber, modelYearIds) + Timestamp now = new Timestamp(new Date().time) + sql().withBatch(50, FITMENT_INSERT_SQL) { stmt -> + modelYearIdsToBeCreated.each { Integer modelYearId -> + stmt.addBatch([modelYearId: modelYearId, partNumber: partNumber, now: now, username: username]) + } + } + } + [errors: errors] + } + + private List modelIdsToBeCreated(String partNumber, List modelYearIds) { + List existingModelYearIds = partFitmentService.findFitmentsByPartNumberAndModelYearIds(partNumber, + modelYearIds, 'modelYearId')*.modelYearId + modelYearIds.parallelStream() + .filter { s -> !existingModelYearIds.contains(s) } + .collect(Collectors.toList()) + } + + /** + * Delete partFitment + * + * @param partNumber + * @param fitmentId + * @param username + * + * @return Map containing errors + */ + Map deleteFitment(String partNumber, Integer fitmentId, String username) { + List partFitment = partFitmentService.findFitmentsByPartNumberAndPartFitmentIds(partNumber, [fitmentId]) + deleteFitments(partFitment ?: [], username) + } + + /** + * Delete partFitments + * + * @param values Map containing partNumber and partFitmentIds + * @param username + * + * @return Map containing errors + */ + Map deleteMultipleFitments(Map values, String username) { + List partFitment = partFitmentService.findFitmentsByPartNumberAndPartFitmentIds(values.partNumber, values.partFitmentIds) + deleteFitments(partFitment ?: [], username) + } + + /** + * Create Fitment position + * + * @param positions + * @param partFitmentIds + * @param username + */ + void assignFitmentPositions(List positions, List partFitmentIds, String username) { + Timestamp now = new Timestamp(new Date().time) + positions.each { Integer position -> + sql().withBatch(50, FITMENT_POSITION_INSERT_SQL) { stmt -> + partFitmentIds.each { Integer fitmentId -> + stmt.addBatch([partFitmentId: fitmentId, positionTypeId: position, now: now, username: username]) + } + } + } + } + + private Map deleteFitments(List partFitments, String username) { + List partFitmentIds = partFitments*.partFitmentId + if (partFitments) { + sql().executeUpdate(deleteFitmentsSql(partFitmentIds), [new Timestamp(new Date().time), username]) + sql().executeUpdate(deleteFitmentPositionSql(partFitmentIds), [new Timestamp(new Date().time), username]) + } + [errors: []] + } + + private static final String FITMENT_INSERT_SQL = ''' +INSERT INTO PartFitment (modelYearId, partNumber, note, dateCreated, createdBy, lastUpdated, lastUpdatedBy) +VALUES (:modelYearId, :partNumber, :note, :now, :username, :now, :username) +''' + + private static final String FITMENT_POSITION_INSERT_SQL = ''' +INSERT INTO PartFitmentPosition (partFitmentId, positionTypeId, dateCreated, createdBy, lastUpdated, lastUpdatedBy) +values (:partFitmentId, :positionTypeId, :now, :username, :now, :username) +''' + + private String deleteFitmentPositionSql(List partFitmentIds) { + "UPDATE PartFitmentPosition SET dateDeleted = ?, deletedBy = ? WHERE partFitmentId IN (${partFitmentIds.join(', ')})" + } + + private String deleteFitmentsSql(List partFitmentIds) { + "UPDATE PartFitment SET dateDeleted = ?, deletedBy = ? WHERE partFitmentId IN (${partFitmentIds.join(', ')})" + } +} Index: grails-app/services/com/lemans/ds/fitment/PartFitmentService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/fitment/PartFitmentService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/fitment/PartFitmentService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,54 @@ +package com.lemans.ds.fitment + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +/** + * Created by vramisetti on 6/13/2017. + */ +@Transactional(readOnly = true) +class PartFitmentService extends LemansManager { + + private static final String FIND_MODEL_SQL = 'EXEC dbo.spGetPartFitment @partNumber = ?, @offset = ?, @pageSize = ?, @sortBy = ?' + + /** + * Find partFitment details by partNumber. + * + * @param criteria containing partNumber and pagination parameters + * + * @return partFitment details + */ + Map findFitmentsByPart(Map criteria) { + List results = sql().callWithAllRows(FIND_MODEL_SQL, + [criteria.partNumber, criteria.offset, criteria.pageSize, criteria.sorting]) { } + [results: results[0][0][0]?.characterStream?.text, totalRecords: results[1][0]?.totalRecords ?: 0] + } + + /** + * Find fitments by partNumber and modelYearIds. + * + * @param partNumber + * @param modelYearIds list + * + * @return List of partFitments + */ + List findFitmentsByPartNumberAndModelYearIds(String partNumber, List modelYearIds, String columns = null) { + String query = """SELECT ${columns ?: '*'} FROM dbo.PartFitment WITH(NOLOCK) + WHERE partNumber = ? AND modelYearId IN (${modelYearIds.join(', ')}) AND dateDeleted IS NULL""" + sql().rows(query, [partNumber]) + } + + /** + * Find fitments by partNumber and partFitmentIds. + * + * @param partNumber + * @param partFitmentIds list + * + * @return List of partFitments + */ + List findFitmentsByPartNumberAndPartFitmentIds(String partNumber, List partFitmentIds) { + String query = """SELECT * FROM dbo.PartFitment WITH(NOLOCK) + WHERE partNumber = ? AND partFitmentId IN (${partFitmentIds.join(', ')}) AND dateDeleted IS NULL""" + sql().rows(query, [partNumber]) + } +} Index: grails-app/services/com/lemans/ds/fitment/make/MakeManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/fitment/make/MakeManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/fitment/make/MakeManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,119 @@ +package com.lemans.ds.fitment.make + +import com.lemans.ds.fitment.Make +import com.lemans.ds.fitment.Segment +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class MakeManagerService extends LemansManager { + + def makeService + + /** + * Creates a new Make. + * + * @param values + * @param username + * + * @return Make which may contain errors or be null + */ + Make createMake(Map values, String username) { + Make make = new Make(values) + make.validate() + validateModelNameFormat(make) + saveOrDiscardDomain(make, username) + } + + /** + * Updates a Make. + * + * @param values + * @param makeId + * @param username + * + * @return Make which may contain errors or be null + */ + Make updateMake(Map values, Integer makeId, String username) { + Make make = findMake(makeId) + if (make) { + applyValuesToDomain(values, make) + validateModelNameFormat(make) + saveOrDiscardDomain(make, username) + } + make + } + + /** + * Soft deletes a Make. + * + * @param makeId + * @param username + * + * @return Make which may contain errors or be null + */ + Make deleteMake(Integer makeId, String username) { + Map partsAndModels = makeService.modelFitmentCount([makeId: makeId]) + Integer partCount = partsAndModels.partCount + Integer modelCount = partsAndModels.modelCount + if (modelCount || partCount) { makeWithErrors(partCount, modelCount) } + else { + Make make = findMake(makeId) + if (make) { softDelete(make, username) } + make + } + } + + /** + * Finds Make by makeId + * @param makeId + * + * @return the Make or null + */ + Make findMake(Integer makeId) { + Make.findByIdAndDateDeletedIsNull(makeId) + } + + void validateModelNameFormat(Object object) { + String modelNameFormat = object.modelNameFormat + if (modelNameFormat) { + List modelNameFormatSegments = modelNameFormat.split('\\P{Alpha}+') + if (modelNameFormatSegments - segmentCodes()) { + invalidSegmentsExist(object) + } + if (modelNameFormatSegments.clone().toUnique().size() != modelNameFormatSegments.size()) { + duplicateSegmentsExist(object) + } + } + } + + private List segmentCodes() { + Segment.withNewSession { + Segment.segmentCodes() + } + } + + private void invalidSegmentsExist(Object object) { + object.errors.rejectValue('modelNameFormat', 'makeModel.modelNameFormat.invalidSegmentCode', 'Invalid Segment codes present') + } + + private void duplicateSegmentsExist(Object object) { + object.errors.rejectValue('modelNameFormat', 'makeModel.modelNameFormat.duplicateSegmentCode', + 'Model Name Format has duplicate Segment Codes') + } + + private Make makeWithErrors(Integer partCount, Integer modelCount) { + Make make = new Make() + if (partCount) { partsWithMakeExists(make, partCount) } + if (modelCount) { modelsWithMakeExists(make, modelCount) } + make + } + + private void modelsWithMakeExists(Make make, Integer modelCount) { + make.errors.reject('make.models.assigned', [modelCount] as Object[], 'Cannot delete Make that has models associated') + } + + private void partsWithMakeExists(Make make, Integer partCount) { + make.errors.reject('make.parts.assigned', [partCount] as Object[], 'Cannot delete Make that has parts associated') + } +} Index: grails-app/services/com/lemans/ds/fitment/make/MakeService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/fitment/make/MakeService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/fitment/make/MakeService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,88 @@ +package com.lemans.ds.fitment.make + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional(readOnly = true) +class MakeService extends LemansService { + + /** + * Finds a Make by id. + * + * @param makeId + * + * @return Map containing Make info + */ + Map findMakeById(Integer makeId) { + dqx([makeId: makeId]).executeOneFrom('dbo.vwMake', ['makeId = :makeId'])?.results[0] + } + + /** + * Finds Makes. + * + * @param criteria + * + * @return Map containing paginated data + */ + Map findMakes(Map criteria) { + criteria.sorting = criteria.sorting ?: 'makeId+ASC' + dqx(criteria).executeFrom('dbo.vwMake', clauses(criteria)) + } + + /** + * Count of Model's And Part's Assigned to a Make. + * + * @param criteria containing makeId + * + * @return Map containing modelCount and partCount + */ + Map modelFitmentCount(Map criteria) { + sql().rows(criteria, '''SELECT x.modelCount + ,y.partCount +FROM ( + SELECT COUNT(*) AS modelCount + FROM dbo.Model mo WITH (NOLOCK) + WHERE mo.makeId = :makeId + AND mo.dateDeleted IS NULL + ) x + ,( + SELECT COUNT(*) AS partCount + FROM dbo.Model mo WITH (NOLOCK) + INNER JOIN dbo.ModelYear my WITH (NOLOCK) ON my.modelId = mo.modelId + AND my.dateDeleted IS NULL + INNER JOIN dbo.PartFitment f WITH (NOLOCK) ON f.modelYearId = my.modelYearId + AND f.dateDeleted IS NULL + WHERE mo.makeId = :makeId + AND mo.dateDeleted IS NULL + ) y''' + )[0] + } + + private List clauses(Map criteria) { + List clauses = [] + clauses.addAll wildcardClauses(criteria, ['makeName']) + if (criteria.q) { + clauses.addAll qClauses(criteria) + } + clauses + } + + private List qClauses(criteria) { + List clauses = [] + if (criteria.q) { + expandQ(criteria) + String clause = ''' + makeName LIKE :beginningWithQ + OR makeName LIKE :containingQ''' + clauses << clause + } + clauses + } + + private expandQ(criteria) { + criteria.with { + beginningWithQ = "$q%".toString() + containingQ = "%$q%".toString() + } + } +} Index: grails-app/services/com/lemans/ds/fitment/model/ModelManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/fitment/model/ModelManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/fitment/model/ModelManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,347 @@ +package com.lemans.ds.fitment + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +import java.sql.Timestamp +import java.time.Year + +@Transactional +class ModelManagerService extends LemansManager { + + def makeManagerService + + def modelService + + def modelYearService + + /** + * Creates a new Model. + * + * @param values containing makeId + * @param username + * + * @return Model which may contain errors or be null + */ + Model createModel(Map values, String username) { + Model model = model(values) + model.validate() + duplicateModelValidation(model) + invalidYearsValidation(values.years, model) + makeManagerService.validateModelNameFormat(model) + saveOrDiscardModel(model, username) + model + } + + private Model model(Map values) { + Make make = makeManagerService.findMake(values.makeId) + Model model = new Model(values) + model.make = make + addSegmentsAndYears(model, values.segments, values.years) + model.modelName = createModelName(model) + model + } + + String createModelName(Model model) { + Map modelSegments = [:] + model.modelSegments.findAll { it.dateDeleted == null }.each { modelSegment -> + if (modelSegment.segment && modelSegment.segmentValue) { + modelSegments << [(modelSegment.segment.sequence): "${modelSegment.segmentValue}[${modelSegment.segment.segmentCode}]"] + } + } + modelSegments ? modelSegments.sort()*.value.join('|') : '' + } + + private Model saveOrDiscardModel(Model model, String username) { + if (model.hasErrors() || deleted(model)) { + model.discard() + } else { + Set modelSegments = model.modelSegments?.clone() + Set modelYears = model.modelYears?.clone() + clearSegmentsAndYears(model) + auditFields(username, model) + model.save() + saveSegmentsAndYears(modelSegments, modelYears, model.id, username) + } + model + } + + private void clearSegmentsAndYears(Model model) { + model.modelSegments?.clear() + model.modelYears?.clear() + } + + private void auditFields(String username, Model model) { + Date now = new Date() + model.lastUpdatedBy = username + model.lastUpdated = now + if (!model.id) { + model.createdBy = username + model.dateCreated = now + } + } + + private void addSegmentsAndYears(Model model, List segments, List years) { + Set segmentMaps = Segment.findAllByDateDeletedIsNull() + segments?.each { segment -> + model.addToModelSegments([segment: segmentMaps.find { it.segmentCode == segment.segmentCode }, segmentValue: segment.value]) + } + years?.each { model.addToModelYears(new ModelYear(year: it)) } + } + + private saveSegmentsAndYears(Set modelSegments, Set modelYears, Integer modelId, String username) { + Timestamp now = new Timestamp(new Date().time) + if (modelSegments) { + sql().withBatch(50, MODEL_SEGMENT_INSERT_SQL) { stmt -> + modelSegments.each { ModelSegment modelSegment -> + stmt.addBatch([modelId: modelId, segmentId: modelSegment.segment.id, value: modelSegment.segmentValue, + now: now, username: username]) + } + } + } + if (modelYears) { + sql().withBatch(50, MODEL_YEAR_INSERT_SQL) { stmt -> + modelYears.each { ModelYear year -> + stmt.addBatch([modelId: modelId, year: year.year, now: now, username: username]) + } + } + } + } + + /** + * Updates a Model. + * + * @param values containing makeId + * @param modelId + * @param username + * + * @return Model which may contain errors or be null + */ + Model updateModel(Map values, Integer modelId, String username) { + Model model = findModelByMake([modelId: modelId, makeId: values.makeId]) + if (model) { + applyValuesToDomain(values, model) + updateSegmentsAndYears(model, values, username) + model.modelName = createModelName(model) + model.validate() + duplicateModelValidation(model) + makeManagerService.validateModelNameFormat(model) + invalidYearsValidation(values.years, model) + saveOrDiscardDomain(model, username) + } + model + } + + private void updateSegmentsAndYears(Model model, Map values, String username) { + updateSegments(model, values.segments, username) + updateYears(model, values.years, username) + } + + private void updateSegments(Model model, List modelSegments, String username) { + if (modelSegments) { + Set segments = Segment.findAllByDateDeletedIsNull() + Set existingModelSegments = model.modelSegments.findAll { it.dateDeleted == null } + modelSegments.each { ms -> + Segment segment = segments.find { it.segmentCode == ms.segmentCode } + ModelSegment modelSegment = existingModelSegments.find { it.segment == segment } + if (modelSegment) { modelSegment.segmentValue = ms.value } + else { + Date now = new Date() + model.addToModelSegments([segmentValue: ms.value, segment: segment, createdBy: username, + dateCreated: now, lastUpdatedBy: username, lastUpdated: now]) + } + } + } + } + + private void updateYears(Model model, List years, String username) { + if (years) { + if (!invalidYears(years)) { + List yearsToInsert = years - model.modelYears.findAll { it.dateDeleted == null }*.year + if (yearsToInsert) { + Timestamp now = new Timestamp(new Date().time) + Integer modelId = model.id + sql().withBatch(50, MODEL_YEAR_INSERT_SQL) { stmt -> + yearsToInsert.each { Integer year -> + stmt.addBatch([modelId: modelId, year: year, now: now, username: username]) + } + } + } + } + } + } + + private List invalidYears(List years) { + years.findAll { (it < 1894) || (it > Year.now().value + 1) } + } + + private void invalidYearsValidation(List years, Model model) { + List invalidYears = invalidYears(years) + if (invalidYears) { + model.errors.reject('model.yearRangeExceeded', [invalidYears, "${Year.now().value + 1}"] as Object[], 'Invalid Years') + } + } + + /** + * Updates Multiple Models. + * operation which is either ADDYEAR, DELETEYEAR or DELETE + * + * @param values + * @param username + * + * @return Model which may contain errors or be null + */ + Map updateMultipleModels(Map values, String username) { + switch (values.operation) { + case 'DELETE': + deleteMultipleModels(values, username) + break + case 'ADDYEAR': + createYearsForModels(values, username) + break + case 'DELETEYEAR': + deleteYearsForModels(values, username) + break + } + } + + /** + * Create Years for Models (Ignore already existing Years). + * + * @param values + * @param username + * + * @return Map containing errors + */ + Map createYearsForModels(Map values, username) { + List errors = [] + modelAndYearsValidation(values, errors) + if (!errors) { + Map modelYears = modelYearService.verifyModelYears([makeId: values.makeId, modelIds: values.modelIds, years: values.years]) + Timestamp now = new Timestamp(new Date().time) + sql().withBatch(50, MODEL_YEAR_INSERT_SQL) { stmt -> + values.modelIds.each { Integer modelId -> + List existingYears = modelYears[modelId]*.year + values.years.each { Integer year -> + if (!(year in existingYears)) { + stmt.addBatch([modelId: modelId, year: year, now: now, username: username]) + } + } + } + } + } + [errors: errors] + } + + private void duplicateModelValidation(Model model) { + if (model.modelName && modelService.modelWithModelNameExists(model)) { + model.errors.rejectValue('modelName', 'model.duplicate', 'Duplicate Model') + } + } + + private void modelAndYearsValidation(Map values, List errors) { + List modelIds = modelService.verifyModelIds([makeId: values.makeId, modelIds: values.modelIds]) + List inValidModelIds = values.modelIds - modelIds + if (inValidModelIds) { errors << "Invalid Model Ids found $inValidModelIds" } + List years = values.years + Integer currentYear = Year.now().value + List invalidYears = years.findAll { (it < 1894) || (it > currentYear + 1) } + if (invalidYears) { errors << "Invalid Years found $invalidYears. Years should be between 1894 and ${currentYear + 1}" } + } + + private Map deleteYearsForModels(Map values, username) { + List errors = [] + modelAndYearsValidation(values, errors) + if (!errors) { + Map modelYears = modelYearService.verifyModelYears([makeId: values.makeId, modelIds: values.modelIds, years: values.years]) + Timestamp now = new Timestamp(new Date().time) + sql().withBatch(50, MODEL_YEAR_DELETE_SQL) { stmt -> + values.modelIds.each { Integer modelId -> + List existingYears = modelYears[modelId]*.year + values.years.each { Integer year -> + if ((year in existingYears)) { + stmt.addBatch([modelId: modelId, year: year, now: now, username: username]) + } + } + } + } + } + [errors: errors] + } + + private Map deleteMultipleModels(Map values, username) { + List errors = [] + Map criteria = [makeId: values.makeId, modelIds: values.modelIds] + List modelIds = modelService.verifyModelIds(criteria) + List invalidModels = values.modelIds - modelIds + if (invalidModels) { errors << "Invalid Model Ids found $invalidModels" } + if (!errors) { + List modelsContainingParts = modelService.modelsContainingParts(criteria) + if (modelsContainingParts) { + errors << "Found Model(s) $modelsContainingParts associated to parts. Cannot delete models that has parts Associated" + } else { + batchDeleteModels([modelIds: values.modelIds.join(','), now: new Timestamp(new Date().time), username: username]) + } + } + [errors: errors] + } + + private void batchDeleteModels(Map criteria) { + sql().withBatch(20) { stmt -> + stmt.addBatch "UPDATE Model ${modelDeleteSql(criteria)}" + stmt.addBatch "UPDATE ModelSegment ${modelDeleteSql(criteria)}" + stmt.addBatch "UPDATE ModelYear ${modelDeleteSql(criteria)}" + } + } + + /** + * Soft deletes a Model. + * + * @param criteria containing modelId and makeId + * @param username + * + * @return Model which may contain errors or be null + */ + Map deleteModel(Map criteria, String username) { + deleteMultipleModels([makeId: criteria.makeId, modelIds: [criteria.modelId]], username) + } + + /** + * Finds Model by modelId + * @param modelId + * + * @return the Model or null + */ + Model findModelByMake(Map criteria) { + Make make = makeManagerService.findMake(criteria.makeId) + Model.findByIdAndMakeAndDateDeletedIsNull(criteria.modelId, make) + } + + private static final String MODEL_YEAR_INSERT_SQL = ''' +INSERT INTO ModelYear (modelId, [year], dateCreated, createdBy, lastUpdated, lastUpdatedBy) +VALUES (:modelId, :year, :now, :username, :now, :username) +''' + + private static final String MODEL_YEAR_DELETE_SQL = ''' +UPDATE ModelYear SET + dateDeleted = :now, + deletedBy = :username +WHERE modelId = :modelId AND year = :year AND dateDeleted IS NULL +''' + + private static final String MODEL_SEGMENT_INSERT_SQL = ''' +INSERT INTO ModelSegment (modelId, segmentId, value, dateCreated, createdBy, lastUpdated, lastUpdatedBy) +VALUES (:modelId, :segmentId, :value, :now, :username, :now, :username) +''' + + private static String modelDeleteSql(Map criteria) { + """ + SET dateDeleted = '$criteria.now', + deletedBy = '$criteria.username', + lastUpdated = '$criteria.now', + lastUpdatedBy = '$criteria.username' + WHERE dateDeleted IS NULL + AND modelId IN ($criteria.modelIds) + """ + } +} Index: grails-app/services/com/lemans/ds/fitment/model/ModelService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/fitment/model/ModelService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/fitment/model/ModelService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,74 @@ +package com.lemans.ds.fitment.model + +import com.lemans.ds.fitment.Model +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional(readOnly = true) +class ModelService extends LemansService { + + private static final String FIND_MODEL_SQL = 'SELECT dbo.fnGetModelDetails(?, ?)' + + /** + * Model with ModelName already Exists. + * + * @param Model + * + * @return true if modelName already exits. + */ + boolean modelWithModelNameExists(Model model) { + String query = "SELECT modelId FROM dbo.vwModel WHERE modelName = ? AND makeId = ? ${model.id ? 'AND modelId <> ?' : ''}" + sql().rows(query, modelNameClauses(model))[0]?.modelId + } + + private List modelNameClauses (Model model) { + List clauses = [model.modelName, model.makeId] + if (model.id) { clauses << model.id } + clauses + } + + /** + * Find modelDetails by makeId and modelId. + * + * @param makeId, modelId + * + * @return Model Details String + */ + String findModelDetails(Integer makeId, Integer modelId = null) { queryForXmlClob(FIND_MODEL_SQL, [makeId, modelId]) } + + /** + * Verify modelId's by makeId. + * + * @param criteria containing makeId and modelIds + * + * @return List of valid modelIds + */ + List verifyModelIds(Map criteria) { + String query = "SELECT modelId FROM dbo.Model WHERE makeId = ? AND modelId IN (${criteria.modelIds.join(', ')})" + sql().rows(query, [criteria.makeId]).modelId + } + + /** + * Verify model's containing parts. + * + * @param criteria containing makeId and modelIds + * + * @return List of models containing parts + */ + List modelsContainingParts(Map criteria) { + String query = """ + SELECT mo.modelId + FROM dbo.Model mo WITH (NOLOCK) + INNER JOIN ModelYear my WITH (NOLOCK) ON mo.modelId = my.modelId + AND my.dateDeleted IS NULL + INNER JOIN PartFitment pf WITH (NOLOCK) ON my.modelYearId = pf.modelYearId + AND pf.dateDeleted is NULL + WHERE mo.makeId = ? + AND mo.modelId IN (${criteria.modelIds.join(', ')}) + AND mo.dateDeleted IS NULL + GROUP BY mo.modelId + HAVING COUNT(*) > 0 + """ + sql().rows(query, [criteria.makeId])?.modelId + } +} Index: grails-app/services/com/lemans/ds/fitment/model/segment/SegmentManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/fitment/model/segment/SegmentManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/fitment/model/segment/SegmentManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,71 @@ +package com.lemans.ds.fitment.model.segment + +import com.lemans.ds.fitment.Model +import com.lemans.ds.fitment.ModelSegment +import com.lemans.ds.fitment.Segment +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +/** + * Created by VRAMISETTI on 4/28/2017. + */ +@Transactional +class SegmentManagerService extends LemansManager { + + def modelManagerService + + def modelService + + ModelSegment updateOrCreateModelSegment(Map criteria, String username) { + Model model = modelManagerService.findModelByMake(criteria) + ModelSegment modelSegment + if (model) { + Segment segment = findSegment(criteria.segmentCode) + modelSegment = findModelSegment(model, segment) ?: new ModelSegment(model: model, + segment: segment, segmentValue: criteria.value) + if (modelSegment.id && !criteria.value) { + String origSegmentValue = modelSegment.segmentValue + validateDuplicateModel(modelSegment, model, segment, criteria, username) + modelSegment.segmentValue = origSegmentValue + modelSegment.hasErrors() ? modelSegment.discard() : softDelete(modelSegment, username) + } else { + modelSegment.segmentValue = criteria.value + modelSegment.validate() + validateDuplicateModel(modelSegment, model, segment, criteria, username) + saveOrDiscardDomain(modelSegment, username) + } + } + modelSegment + } + + private void validateDuplicateModel(ModelSegment ms, Model model, Segment segment, Map criteria, String username) { + ModelSegment modelSegment = model.modelSegments.find { it.segment == segment && it.dateDeleted == null } + if (modelSegment) { + modelSegment.segmentValue = criteria.value + } else { + Date now = new Date() + model.addToModelSegments([segmentValue: criteria.value, segment: segment, createdBy: username, + dateCreated: now, lastUpdatedBy: username, lastUpdated: now]) + } + duplicateModelError(model, ms) + model.discard() + } + + private void duplicateModelError(Model model, ModelSegment ms) { + model.modelName = modelManagerService.createModelName(model) + if (!model.modelName) { + ms.errors.reject('model.emptySegments', 'Empty Segments') + } + if (model.modelName && model.id && modelService.modelWithModelNameExists(model)) { + ms.errors.reject('model.duplicate', 'Duplicate Model') + } + } + + Segment findSegment(String segmentCode) { + Segment.findBySegmentCodeAndDateDeletedIsNull(segmentCode) + } + + ModelSegment findModelSegment(Model model, Segment segment) { + ModelSegment.findByModelAndSegmentAndDateDeletedIsNull(model, segment) + } +} Index: grails-app/services/com/lemans/ds/fitment/model/segment/SegmentService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/fitment/model/segment/SegmentService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/fitment/model/segment/SegmentService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,60 @@ +package com.lemans.ds.fitment.model.segment + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional(readOnly = true) +class SegmentService extends LemansService { + + /** + * Find Segments. + * + * @param criteria + * + * @return Map containing paginated data + */ + Map findSegments(Map criteria) { + criteria.sorting = criteria.sorting ?: 'segmentId+ASC' + dqx(criteria).executeFrom('dbo.Segment', ['dateDeleted IS NULL']) + } + + /** + * Finds Segment values by Make and SegmentCode. + * + * @param criteria containing segmentCode and makeId + * + * @return Map containing paginated data + */ + Map findSegmentValuesBySegmentCode(Map criteria) { + criteria.sorting = criteria.sorting ?: 'modelSegmentId+ASC' + dqx(criteria).executeFrom('dbo.vwModelSegment', clauses(criteria) + ['segmentCode = :segmentCode', 'makeId = :makeId']) + } + + private List clauses(Map criteria) { + List clauses = [] + clauses.addAll wildcardClauses(criteria, ['value']) + if (criteria.q) { + clauses.addAll qClauses(criteria) + } + clauses + } + + private List qClauses(criteria) { + List clauses = [] + if (criteria.q) { + expandQ(criteria) + String clause = ''' + value LIKE :beginningWithQ + OR value LIKE :containingQ''' + clauses << clause + } + clauses + } + + private expandQ(criteria) { + criteria.with { + beginningWithQ = "$q%".toString() + containingQ = "%$q%".toString() + } + } +} Index: grails-app/services/com/lemans/ds/fitment/model/year/ModelYearManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/fitment/model/year/ModelYearManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/fitment/model/year/ModelYearManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,70 @@ +package com.lemans.ds.fitment.model.year + +import com.lemans.ds.fitment.ModelYear +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +import java.sql.Timestamp + +@Transactional +class ModelYearManagerService extends LemansManager { + + def modelYearService + + def modelManagerService + + /** + * Creates a new ModelYear. + * + * @param values containing makeId, modelId + * @param username + * + * @return ModelYear which may contain errors or be null + */ + Map createMultipleYears(Map values, String username) { + Map criteria = [makeId: values.makeId, modelIds: [values.modelId], years: values.years] + modelManagerService.createYearsForModels(criteria, username) + } + + /** + * Soft deletes ModelYears. + * + * @param criteria containing makeId, modelId, years + * + * @return Map containing errors + */ + Map deleteMultipleYears(Map values, String username) { + List years = values.years + List modelYears = modelYearService.yearIdsByYears(values.modelId, years) + deleteYearsAndFitments(modelYears, username, values) + } + + /** + * Soft deletes ModelYear. + * + * @param criteria containing makeId, modelId, yearId + * + * @return Map containing errors + */ + Map deleteYear(Map values, String username) { + Map modelYear = modelYearService.findYearById(values) + deleteYearsAndFitments(modelYear ? [modelYear] : [], username, values) + } + + private Map deleteYearsAndFitments(List modelYears, String username, Map values) { + List modelYearIds = modelYears*.modelYearId + if (modelYears) { + ModelYear.executeUpdate(deleteYearsSql(modelYearIds), [new Date(), username, values.modelId]) + sql().executeUpdate(deleteFitmentsSql(modelYearIds), [new Timestamp(new Date().time), username]) + } + [errors: []] + } + + private String deleteYearsSql(List modelYearIds) { + "UPDATE ModelYear SET dateDeleted = ?, deletedBy = ? WHERE modelId = ? AND modelYearId IN (${modelYearIds.join(', ')})" + } + + private String deleteFitmentsSql(List modelYearIds) { + "UPDATE PartFitment SET dateDeleted = ?, deletedBy = ? WHERE modelYearId IN (${modelYearIds.join(', ')})" + } +} Index: grails-app/services/com/lemans/ds/fitment/model/year/ModelYearService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/fitment/model/year/ModelYearService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/fitment/model/year/ModelYearService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,92 @@ +package com.lemans.ds.fitment.model.year + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional(readOnly = true) +class ModelYearService extends LemansService { + + + /** + * Finds a Year by id. + * + * @param criteria Map containing yearId, modelId and makeId + * + * @return Map containing Model Year info + */ + Map findYearById(Map criteria) { + dqx(criteria).executeOneFrom('dbo.ModelYear', makeModel() + ['modelYearId = :yearId'] )?.results[0] + } + + /** + * Finds Years by ModelId. + * + * @param criteria Map containing yearId, modelId and makeId + * + * @return Map containing Model Year info + */ + Map verifyModelYears(Map criteria) { + String query = """SELECT modelId, [year] FROM dbo.ModelYear WHERE modelId IN (${criteria.modelIds.join(', ')}) + AND [year] IN (${criteria.years.join(', ')}) AND dateDeleted IS NULL""" + sql().rows(query)?.groupBy { it.modelId } + } + + /** + * Finds ModelYears by years. + * + * @param modelId + * @param years + * + * @return List containing modelYearId and year + */ + List yearIdsByYears(Integer modelId, List years) { + String columns = 'modelYearId, [year]' + sql().rows(yearIdsSql(years, columns), [modelId]) + } + + /** + * Finds ModelYears by years. + * + * @param modelId + * @param years + * + * + * @return List containing modelYears + */ + List findModelYearsByYears(Integer modelId, List years) { + sql().rows(yearIdsSql(years), [modelId]) + } + + /** + * Finds Years. + * + * @param criteria + * + * @return Map containing paginated data + */ + Map findYears(Map criteria) { + criteria.sorting = criteria.sorting ?: 'modelYearId+ASC' + dqx(criteria).executeFrom('dbo.ModelYear', makeModel()) + } + + /** + * Finds valid ModelYearIds. + * + * @param modelYearIds + * + * @return List of valid modelYearIds + */ + List findValidModelYearIds(List modelYearIds) { + String query = """SELECT modelYearId FROM dbo.ModelYear + WHERE modelYearId IN (${modelYearIds.join(', ')}) AND dateDeleted IS NULL""" + sql().rows(query) + } + + //TODO: to remove datedeleted and modelId back to the clause. + private List makeModel() { [/*'makeId = :makeId', */'modelId = :modelId', 'dateDeleted IS NULL'] } + + private String yearIdsSql(List years, String columns = null) { + """SELECT ${columns ?: '*'} FROM dbo.ModelYear + WHERE modelId = ? AND [year] IN (${years.join(', ')}) AND dateDeleted IS NULL""" + } +} Index: grails-app/services/com/lemans/ds/flag/FlagService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/flag/FlagService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/flag/FlagService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,38 @@ +package com.lemans.ds.flag + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional(readOnly = true) +class FlagService extends LemansService { + + /** + * Find Flags. + * + * @param criteria Map + * + * @return Map containing List of Flags and totalRecords + */ + Map findFlags(Map criteria) { + dqx(criteria).executeFrom('dbo.vwFlag', clauses(criteria)) + } + + /** + * Find valid FlagIds by entityClass. + * + * @param List containing flagIds + * + * @param entityClass + * + * @return List containing valid flagIds by entityClass + */ + List validFlagIdsForEntityClass(List flagIds, String entityClass) { + sql().rows("SELECT flagId FROM vwFlag WHERE entityClass = $entityClass AND flagId IN (" + flagIds.join(',') + ')')?.flagId + } + + private clauses(Map criteria) { + List clauses = [] + if (criteria.entityClass) { clauses << 'entityClass = :entityClass' } + clauses + } +} Index: grails-app/services/com/lemans/ds/flag/FlagValueManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/flag/FlagValueManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/flag/FlagValueManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,113 @@ +package com.lemans.ds.flag + +import com.lemans.ds.bulk.BulkProcessingHelperService +import com.lemans.ds.media.Media +import grails.transaction.Transactional + + +@Transactional +class FlagValueManagerService extends BulkProcessingHelperService { + + def flagService + + def flagValueService + + def partService + + /** + * Add or delete Flag Value for an entityClass and entityId. + * + * @param Map containing flagIds, entityClass and entityId + * + * @param userName + * + * @return Map containing errors + */ + Map addOrDeleteFlagValues(Map values, String username) { + List errors = [] + List flagIds = values.flagIds + String entityClass = values.entityClass + String entityId = values.entityId + + validateFlagIds(flagIds, entityClass, errors) + customEntityClassValidations(entityClass, entityId, errors) + + if (!errors) { + switch (values.operation) { + case 'DELETE': + deleteFlagValues(flagIds, entityId, errors, username) + break + case 'INSERT': + default: + addFlagValues(flagIds, entityId, errors, username) + } + } + [errors: errors] + } + + private void customEntityClassValidations(String entityClass, String entityId, List errors) { + switch (entityClass) { + case 'part': + validatePart(entityId, errors) + break + case 'media': + validateMedia(entityId, errors) + break + } + } + + private void validateFlagIds(List flagIds, String entityClass, List errors) { + if (flagIds) { + List inValidFlagIds = flagIds - flagService.validFlagIdsForEntityClass(flagIds, entityClass) + if (inValidFlagIds) { + errors << "Invalid FlagIds found $inValidFlagIds" + } + } else { errors << 'No FlagIds found' } + } + + private void validatePart(String partNumber, List errors) { + if (!partService.partWithPartNumberExists(partNumber)) { + errors << "Part with partNumber $partNumber does not exist" + } + } + + private void validateMedia(String mediaId, List errors) { + if (!(Media.findByIdAndDateDeletedIsNull(mediaId.toInteger()))) { + errors << "Media with mediaId $mediaId does not exist" + } + } + + private void addFlagValues(List flagIds, String entityId, List errors, String username) { + FlagValue.withTransaction { status -> + flagValuesThatNeedToBeCreated(flagIds, entityId).each { Integer flagId -> + FlagValue flagValue = findFlagValue(entityId, flagId) ?: new FlagValue(flagId: flagId) + flagValue.entityId = entityId + flagValue.validate() + saveOrDiscardDomain(flagValue, username) + collectIfErrors(flagValue, errors) + } + if (errors) { status.setRollbackOnly() } + } + } + + private List flagValuesThatNeedToBeCreated(List flagIds, String entityId) { + flagIds - flagValueService.existingFlagIdsForEntityId(flagIds, entityId) + } + + private void deleteFlagValues(List flagIds, String entityId, List errors, String username) { + FlagValue.withTransaction { status -> + flagIds.each { Integer flagId -> + FlagValue flagValue = findFlagValue(entityId, flagId) + if (flagValue) { + softDelete(flagValue, username) + collectIfErrors(flagValue, errors) + } + } + if (errors) { status.setRollbackOnly() } + } + } + + private FlagValue findFlagValue(String partNumber, Integer flagId) { + FlagValue.findByEntityIdAndFlagIdAndDateDeletedIsNull(partNumber, flagId) + } +} Index: grails-app/services/com/lemans/ds/flag/FlagValueService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/flag/FlagValueService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/flag/FlagValueService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,65 @@ +package com.lemans.ds.flag + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional(readOnly = true) +class FlagValueService extends LemansService { + + /** + * Find FlagValue by entityClass, entityId and flagId. + * + * @param Map containing entityClass, entityId and flagId + * + * @return FlagValue for an entityClass, entityId and flagId + */ + Map findById(Map criteria) { + dqx(criteria).executeOneFrom('dbo.vwFlagValue', clauses(criteria))?.results[0] + } + + /** + * Find FlagValues by entityClass, entityId. + * + * @param Map containing entityClass, entityId + * + * @return Map containing FlagValues for an entityClass and entityId and totalRecords + */ + Map findFlags(Map criteria) { + dqx(criteria).executeFrom('dbo.vwFlagValue', clauses(criteria)) + } + + + /** + * Find Existing FlagIds for an entityId. + * + * @param List containing flagIds + * * + * @param entityId + * + * @return List containing flagIds for an entityId + */ + List existingFlagIdsForEntityId(List flagIds, String entityId) { + sql().rows("SELECT flagId FROM vwFlagValue WHERE entityId = $entityId AND flagId IN (" + flagIds.join(',') + ')')?.flagId + } + + private List clauses(Map criteria) { + List clauses = ['entityClass = :entityClass', 'entityId = :entityId'] + if (criteria.flagId) { clauses << 'flagId = :flagId' } + clauses + } + + /** + * Find List of FlagValues for a productId + * + * @param productId and EntityClass + * + * @return List of FlagValues for a productId + */ + List getListOfFlags(Map criteria) { + List clauses = [] + clauses << 'entityClass = :entityClass' + clauses << 'entityId = :entityId' + if (criteria.flagId) { clauses << 'flagId = :flagId' } + dqx(criteria).executeFrom('dbo.vwflagValue', clauses).results + } +} Index: grails-app/services/com/lemans/ds/importing/BulkImportManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/importing/BulkImportManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/importing/BulkImportManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,139 @@ +package com.lemans.ds.importing + +import com.lemans.services.LemansManager +import grails.transaction.Transactional +import javax.annotation.Resource +import java.nio.file.Files + +@SuppressWarnings(['UnusedPrivateMethodParameter', 'UnusedPrivateMethod', 'PrintStackTrace'])//TODO: Code narc to be cleaned +@Transactional +class BulkImportManagerService extends LemansManager { + + private static final String MIME_TYPE_SQL = ''' +SELECT * +FROM dbo.vwMimeType +WHERE mimeType = ? +AND extension = ? +''' + + @Resource(name='dsLocaleImport') + def dsLocaleImport + + @Resource(name='temporaryMediaPath') + def temporaryMediaPath + + def bulkImportService + + def mediaManagerService + + /** + * Adds an Excel File. + * + * @param values Map containing tempId, originalFileName, and optionally description and effectiveDate + * @param username + * + * @return Excel which may be null or contain errors + */ + LocaleImportProcess addLocaleImportProcessFile(Map values, String username) { + String tempId = values.tempId + values.remove('tempId') + LocaleImportProcess localeImportProcess = new LocaleImportProcess(values) + localeImportProcess.originalFileName = values.originalFileName + String tempFilePath = tempId + '.' + values.extension + String pathToTempFile = temporaryMediaPath + '/' + tempFilePath + localeImportProcess.status = 'Uploaded' + + File tempFile = new File(pathToTempFile) + if (tempFile.exists()) { + resolveMimeAndMediaType(tempFile, tempFilePath, values, localeImportProcess) + } + else { localeImportProcess.errors.reject('file.notFound', 'Uploaded file not found') } + saveOrDiscardDomain(localeImportProcess, username) + if (!localeImportProcess.hasErrors()) { + if (!moveFile(tempFile, localeImportProcess, values)) { + softDelete(localeImportProcess, username) + localeImportProcess.errors.reject('file.notMoved', 'Could not Move Uploaded File') + } + } + localeImportProcess + } + + + private void resolveMimeAndMediaType(File tempFile, String tempFilePath, Map values, LocaleImportProcess localeImportProcess) { + String mimeTypeDescription = mediaManagerService.detectMimeType(tempFile, tempFilePath) + Map mimeType = sql().firstRow(MIME_TYPE_SQL, [mimeTypeDescription, values.extension ?: '']) + if (mimeType) { + localeImportProcess.mimeTypeId = mimeType.mimeTypeId + localeImportProcess.validate() + } + else { + String message = "Could not determine the correct file type for mimeType $mimeTypeDescription with extension $values.extension" + localeImportProcess.errors.reject('type.notFound', message) + } + } + + private String completeMediaPath(LocaleImportProcess localeImportProcess) { + dsLocaleImport + localeImportProcess.id + } + + private String pathToExcel(LocaleImportProcess localeImportProcess, String extension) { + completeMediaPath(localeImportProcess) + '/' + absoluteFileName(localeImportProcess, extension) + } + + private String absoluteFileName(LocaleImportProcess localeImportProcess, String extension) { + "${localeImportProcess.originalFileName}.${extension}" + } + + @SuppressWarnings(['CatchException']) + private boolean moveFile(File tempFile, LocaleImportProcess localeImportProcess, Map values) { + // TODO: subfolders - 2 chars & 2 levels, + try { + new File(completeMediaPath(localeImportProcess)).mkdir() + File movedFile = new File(pathToExcel(localeImportProcess, values.extension)) + File localePathFolder = new File(dsLocaleImport + localeImportProcess.id) + if (!movedFile.exists()) { + localePathFolder.mkdirs() + } + Files.copy(tempFile.toPath(), movedFile.toPath()) + true + } catch (Exception x) { + log.error('Unable to move Excel file', x) + x.printStackTrace() + false + } + } + + /** + * Delete Excel File if appropriate. + * + * @param values + * @param localeImportProcessId + * @param username + * + * @return Media which may be null or contain errors + */ + LocaleImportProcess deleteLocaleImportProcess(Integer importFileId, String username) { + Map process = bulkImportService.findLocaleImportProcessById([localeImportProcessId: importFileId]) + if (process && process.endDate) { + LocaleImportProcess localeImportProcess = bulkImportService.findLocaleImportProcess(importFileId) + if (localeImportProcess) { + File file = bulkImportService.findLocaleImportProcessOriginalFile(localeImportProcess) + if (file && file.exists()) { + boolean deleted = file.delete() + if (!deleted) { + log.error('FAILED to delete ' + absoluteFileName(localeImportProcess, localeImportProcess.extension)) + } + softDelete(localeImportProcess, username) + } else { + localeImportProcess.errors.reject('file.notFound', 'Could not delete Excel file. File Does not Exist') + } + } + localeImportProcess + } else { + LocaleImportProcess localeImportProcess1 = new LocaleImportProcess() + localeImportProcess1.errors.reject('localeImportProcess.id.stillProcessing', 'Still Processing') + localeImportProcess1 + } + } + +} Index: grails-app/services/com/lemans/ds/importing/BulkImportService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/importing/BulkImportService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/importing/BulkImportService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,111 @@ +package com.lemans.ds.importing + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +import javax.annotation.Resource + +@Transactional(readOnly = true) +class BulkImportService extends LemansService { + + @Resource(name='mediaPrefix') + def mediaPrefix + + @Resource(name='dsLocaleImport') + def dsLocaleImport + + private static final String IMPORT_PROCESS_SQL = '''SELECT * FROM dbo.vwLocaleImportProcess WITH(NOLOCK) +WHERE localeImportProcessId = :localeImportProcessId''' + + /** + * Finds Locale Import Process by Id. + * + * @param criteria containing localeImportProcessId + * + * @return Map containing Media info + */ + Map findLocaleImportProcessById(Map criteria) { + sql().firstRow(criteria, IMPORT_PROCESS_SQL) + } + + /** + * Finds Locale Import Processes. + * + * @param criteria + * + * @return Map containing paginated data + */ + Map findBulkImports(Map criteria) { + criteria.sorting = criteria.sorting ?: 'localeImportProcessId+ASC' + dqx(criteria).executeFrom('dbo.vwLocaleImportProcess', clauses(criteria)) + } + + /** + * Finds a LocaleImportProcess File. + * + * @param media + * + * @return + */ + File findLocaleImportProcessOriginalFile(Map localeImportProcess) { + File file = new File(originalFilePath(localeImportProcess)) + file.exists() ? file : null + } + + /** + * Finds a LocaleImportProcess File. + * + * @param media + * @return + */ + File findLocaleImportProcessOriginalFile(LocaleImportProcess localeImportProcess) { + File file = new File(originalFilePath(localeImportProcess)) + file.exists() ? file : null + } + + private String originalFilePath(Map localeImportProcess) { + dsLocaleImport + localeImportProcess.localeImportProcessId + '/' + + "${localeImportProcess.originalFileName}.${localeImportProcess.extension}" + } + + private String reportFilePath(Map localeImportProcess) { + dsLocaleImport + localeImportProcess.localeImportProcessId + '/' + 'report.xls' + } + + private String originalFilePath(LocaleImportProcess localeImportProcess) { + dsLocaleImport + localeImportProcess.id + '/' + absoluteFileName(localeImportProcess) + } + + private String absoluteFileName(LocaleImportProcess localeImportProcess) { + "${localeImportProcess.originalFileName}.${localeImportProcess.extension}" + } + + /** + * Finds a LocaleImportProcess File. + * + * @param media + * @return + */ + File findLocaleImportProcessReportFile(Map localeImportProcess) { + File file = new File(reportFilePath(localeImportProcess)) + file.exists() ? file : null + } + + /** + * Find LocaleImportProcess by importFileId. + * + * @param importFileId + * + * @return LocaleImportProcess + */ + LocaleImportProcess findLocaleImportProcess(Integer importFileId) { + LocaleImportProcess.findByIdAndDateDeletedIsNull(importFileId) + } + + + private List clauses(Map criteria) { + List clauses = [] + if (criteria.importType) { clauses << 'importType = :importType' } + clauses + } +} Index: grails-app/services/com/lemans/ds/part/PartAssociationManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/part/PartAssociationManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/part/PartAssociationManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,72 @@ +package com.lemans.ds.part + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class PartAssociationManagerService extends LemansManager { + + def partAssociationService + + /** + * + * Adds PartAssociation + * + * @param partNumber + * @param input + * @param username + * + * @return partAssociation + */ + PartAssociation addPartAssociation(String partNumber, Map input, String username) { + PartAssociation partAssociation = new PartAssociation() + input.partNumber = partNumber + applyValuesToDomain(input, partAssociation) + partAssociation.validate() + validatePartAssociation(partAssociation) + saveOrDiscardDomain(partAssociation, username) + } + + /** + * Updates PartAssociation + * + * @param partAssociationId + * @param input + * @param username + * + * @return partAssociation + */ + PartAssociation updatePartAssociation(Integer id, Map input, String username) { + PartAssociation partAssociation = partAssociationService.getAssociation(id) + if (partAssociation) { + applyValuesToDomain(input, partAssociation) + partAssociation.validate() + saveOrDiscardDomain(partAssociation, username) + } + } + + /** + * Deletes a partAssociation + * + * @param partAssociationId + * @param username + * + * @return partAssociation + */ + PartAssociation deletePartAssociation(Integer id, String username) { + PartAssociation partAssociation = partAssociationService.getAssociation(id) + if (partAssociation) { + softDelete(partAssociation, username) + } + partAssociation + } + + private void validatePartAssociation(PartAssociation partAssociation) { + if (PartAssociation.findByPartNumberAndRelatedPartNumberAndAssociationTypeIdAndDateDeletedIsNull(partAssociation.partNumber, + partAssociation.relatedPartNumber, partAssociation.associationTypeId)) { + partAssociation.errors.reject('partAssociation.duplicate', + [partAssociation.partNumber, partAssociation.associationTypeId, partAssociation.relatedPartNumber] as Object[], + 'PartAssociation for partNumber with associationTypeId and relatedPartNumber exists.') + } + } +} Index: grails-app/services/com/lemans/ds/part/PartAssociationService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/part/PartAssociationService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/part/PartAssociationService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,61 @@ +package com.lemans.ds.part + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional +class PartAssociationService extends LemansService { + + /** + * Find relatedParts by Part Number + * + * @param criteria containing partNumber + * + * @return related parts + */ + Map findRelatedParts(Map criteria) { + criteria.sorting = criteria.sorting ?: 'partNumber+ASC' + dqx(criteria).executeFrom('dbo.vwPartAssociation', clauses(criteria)) + } + + /** + * Find referralParts by related Part Number + * + * @param criteria containing referral partNumber + * + * @return referral parts + */ + Map findReferralParts(Map criteria) { + criteria.sorting = criteria.sorting ?: 'relatedPartNumber+ASC' + dqx(criteria).executeFrom('dbo.vwPartAssociation', clauses(criteria)) + } + + /** + * Find PartAssociation by PartAssociationId + * + * @param criteria contaning partAssociationId + * + * @return partAssociation + */ + Map findPartAssociation(Map criteria) { + dqx(criteria).executeOneFrom('dbo.vwPartAssociation', clauses(criteria))?.results[0] + } + + private List clauses(Map criteria) { + List clauses = [] + if (criteria.partNumber) { + clauses << 'partNumber = :partNumber' + } + if (criteria.relatedPartNumber) { + clauses << 'relatedPartNumber = :relatedPartNumber' + } + if (criteria.partAssociationId) { + clauses << 'partAssociationId = :partAssociationId' + } + clauses + } + + PartAssociation getAssociation(Integer id) { + PartAssociation.findByIdAndDateDeletedIsNull(id) + } +} Index: grails-app/services/com/lemans/ds/part/PartAttributeManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/part/PartAttributeManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/part/PartAttributeManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,92 @@ +package com.lemans.ds.part + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class PartAttributeManagerService extends LemansManager { + + def productPartManagerService + def productPartService + + private final PartAttributeTransformer partAttributeTransformer = new PartAttributeTransformer() + + private static final String UPDATE_PART_ATTRIBUTE_SQL = + '''EXEC spUpdatePartAttribute + @xml = ?, + @appSecurityUser = ? + ''' + + Map addSinglePartAttributeRelation(Map values, String userName) { + values.operation = 'INSERT' + values.attributeValueIds = [values.attributeValueId] + Map input = buildBulkOperationFormat(values) + bulkAddOrRemove(input, userName) + + } + + Map removeSinglePartAttributeRelation(Map values, String userName) { + values.operation = 'DELETE' + values.attributeValueIds = [values.attributeValueId] + Map input = buildBulkOperationFormat(values) + bulkAddOrRemove(input, userName) + } + + Map bulkReplaceAttributeValueRelation(Map values, String userName) { + values.operation = 'REPLACE' + Map input = buildBulkOperationFormat(values) + bulkAddOrRemove(input, userName) + } + + Map buildBulkOperationFormat(Map data) { + Map attributeNameMap = [attributeNameId: data.attributeNameId, attributeValueIds: data.attributeValueIds] + [ + operation: data.operation, + partNumber: [data.partNumber], + attribute: [attributeNameMap] + ] + } + + Map bulkAddOrRemove(Map input, String userName) { + Map error = validateInput(input) + if (error) { + [messages: [error]] + } else { + String inputXML = partAttributeTransformer.partAttributeToXML(input) + List results = sql().callWithAllRows(UPDATE_PART_ATTRIBUTE_SQL, [inputXML, userName]) { } + if (!isErrorFromProc(results)) { + Map products = productPartService.findProductByPartNumber(input) + Map productIds = [:] + List prdIds = [] + products.results.each { Map result -> + prdIds << result.productId + } + productIds << ['productIds': prdIds] + productPartManagerService.processEntityFlag(productIds) + } + [messages: results[0]] + } + } + + Boolean isErrorFromProc(List results) { + Boolean error = true + results.each { List result -> + result.each { Map res -> + if (res.keySet().contains('type') && res.type == 'error') { error = true } + else { + error = false + } + } + } + error + } + + Map validateInput(Map input) { + if (!input || !input.partNumber || input.partNumber?.isEmpty()) { + return [type: 'error', text: 'No Part Selected'] + } + if (!input.attribute || input.attribute?.isEmpty()) { + return [type: 'error', text: 'No attribute change selected'] + } + } +} Index: grails-app/services/com/lemans/ds/part/PartAttributeService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/part/PartAttributeService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/part/PartAttributeService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,32 @@ +package com.lemans.ds.part + +import com.lemans.ds.ReportAndLocaleCommonsController +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional +class PartAttributeService extends LemansService { + + private static final String CATEGORY_DETAILS_SQL = 'SELECT dbo.fnGetCategoryPartDetailsJSON(?,?)' + + private static final String CATEGORY_DETAILS_LOCALE_SQL = 'SELECT dbo.fnGetCategoryPartDetailsJSONLocale(?,?,?)' + + def categoryService + private final PartAttributeTransformer partAttributeTransformer = new PartAttributeTransformer() + + def partAttributeDetails(Map criteria, List partNumber) { + if (categoryService.findCategoryById(criteria)) { + String partXML = partAttributeTransformer.partListToXML(partNumber) + queryForXmlClob(CATEGORY_DETAILS_SQL, [criteria.id, partXML]) + } else { + null + } + } + + String partAttributeLocaleDetails(Map criteria, List partNumber) { + if (criteria.locale in ReportAndLocaleCommonsController.VALID_LOCALES && categoryService.findCategoryById(criteria)) { + String partXML = partAttributeTransformer.partListToXML(partNumber) + queryForXmlClob(CATEGORY_DETAILS_LOCALE_SQL, [criteria.id, partXML, criteria.locale]) + } + } +} Index: grails-app/services/com/lemans/ds/part/PartManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/part/PartManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/part/PartManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,140 @@ +package com.lemans.ds.part + +import com.lemans.ds.category.Category +import com.lemans.ds.media.Media +import com.lemans.ds.part.partmetadata.PartMetadata +import com.lemans.services.LemansManager +import grails.transaction.Transactional +import org.apache.commons.collections.CollectionUtils + +import java.sql.Timestamp + +@Transactional +class PartManagerService extends LemansManager { + + def categoryService + def productPartManagerService + def partService + + private static final List PART_METADATA_FIELDS = ['partSpecificText', 'internalNotes', 'relatedParts', + 'relatedProducts', 'certificationUS', 'certificationEU', + 'oemPartNumber'] + + Map addOrRemoveParts(Map values, String username) { + Integer categoryId = values.categoryId + List partNumbers = values.partNumbers*.toString()*.toUpperCase() + List errors = [] + if (categoryService.categoryExists([catalogInstanceId: values.catalogInstanceId, categoryId: categoryId])) { + List parts = parts(partNumbers) + if (partNumbers.size() == parts.size()) { + Timestamp now = new Timestamp(new Date().time) + Integer cId = (values.action == 'add') ? categoryId : null + sql().execute("UPDATE Part SET categoryId = $cId, lastUpdated = $now, lastUpdatedBy = $username WHERE partNumber IN " + + "('${parts.join('\',\'')}')") + } else { errors << "Invalid parts found ${partNumbers - parts*.toUpperCase()}" } + } else { errors << 'Invalid category' } + [errors: errors] + } + + /** + * Updates specific details on existing Part. + + * @return Part which may have errors or be null + */ + Part updatePart(Map values, String partNumber, String username) { + Part part = findDomainObjectById(Part, partNumber) + if (part) { + applyValuesToDomain(values, part) + part.validate() + validatePart(values, part) + //update productPart relation for product and part + if (values.containsKey('productId') && !part.hasErrors()) { + Map data = updateProductPartRelation(values, partNumber, username) + data.errors.each { + part.errors.reject('invalid', it) + } + } + saveOrDiscardDomain(part, username) + List keys = values.keySet() as String[] + if (CollectionUtils.containsAny(keys, PART_METADATA_FIELDS)) { + updateorInsertPartMetadata(values, partNumber, username) + } + } + part + } + + PartLocale updatePartLocale(Map values, String partNumber, String username) { + PartLocale partLocale = getPartLocale(partNumber, values) + if (partLocale) { + applyValuesToDomain(values, partLocale) + partLocale.validate() + saveOrDiscardDomain(partLocale, username) + } + } + + private PartLocale getPartLocale(String partNumber, Map values) { + PartLocale partLocale = findPartLocale(partNumber, values.locale) + if (!partLocale) { + Part part = Part.findByIdAndDateDeletedIsNull(partNumber) + if (part) { + values.partNumber = partNumber + partLocale = new PartLocale() + } + } + partLocale + } + + private PartLocale findPartLocale(String partNumber, String locale) { + PartLocale.findByPartNumberAndLocaleAndDateDeletedIsNull(partNumber, locale) + } + + /** + * Updates specific details on existing Part. + * + * @return Part which may have errors or be null + */ + PartMetadata updateorInsertPartMetadata(Map values, String partNumber, String username) { + PartMetadata partMetadata = findDomainObjectById(PartMetadata, partNumber) ?: new PartMetadata(values) + Date now = new Date() + if (partMetadata) { + partMetadata.id = partNumber + partMetadata.createdBy = username + partMetadata.dateCreated = now + applyValuesToDomain(values, partMetadata) + partMetadata.validate() + saveOrDiscardDomain(partMetadata, username) + } + } + + /** + * On providing productId, add productPart operation shall be performed. + * On empty or Null productId, remove productPart operation shall be performed. + * @param values + * @param partNumber + * @param username + * @return + */ + Map updateProductPartRelation(Map values, String partNumber, String username) { + values.partNumber = partNumber + if (values.productId && !values.producId.toString().isEmpty()) { + return productPartManagerService.addSingleProductPartRelation(values, username) + } + //retrieve part details to get primaryProductId since productId not provided in payload + Map part = partService.findPartByPartNumber(values) + values.productId = part?.primaryProductId + productPartManagerService.removeSingleProductPartRelation(values, username) + } + + private void validatePart(Map values, Part part) { + if (values.categoryId != null && !findDomainObjectById(Category, values.categoryId)) { + part.errors.rejectValue('categoryId', 'invalid', "CategoryId with value [$values.categoryId] does not exist") + } + if (values.primaryMediaId != null && !findDomainObjectById(Media, values.primaryMediaId)) { + part.errors.rejectValue('primaryMediaId', 'invalid', "PrimaryMediaId with value [$values.primaryMediaId] does not exist") + } + } + + private List parts(List partNumbers) { + sql().rows("SELECT partNumber FROM Part WHERE partNumber IN ('${partNumbers.join('\',\'')}')")*.partNumber + } +} Index: grails-app/services/com/lemans/ds/part/PartService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/part/PartService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/part/PartService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,146 @@ +package com.lemans.ds + +import com.lemans.ds.part.Part +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional(readOnly = true) +class PartService extends LemansService { + + static final List SEARCH_COLUMNSET = [ + 'partNumber', 'punctuatedPartNumber', 'partStatusCode', 'partStatusDescr', 'vendorPartNumber', + 'brandName', 'partDescr', 'brandCode', 'vendorName', 'vendorId', 'subComCode', 'categoryId', + 'subComCodeId', 'productCode', 'mediaUrl', 'extension', 'version' + ].asImmutable() + + private static final List SEARCHABLE_PROPERTIES = [ + 'partNumber', 'partDescr', 'brandCode', 'brandName', + 'vendorId', 'vendorName', 'vendorPartNumber' + ].asImmutable() + + private static final List META_DATA = ['partSpecificText', 'internalNotes', 'relatedParts', + 'relatedProducts', 'certificationUS', 'certificationEU', 'oemPartNumber'] + + private static final Map COLUMN_SETS = [_search: SEARCH_COLUMNSET].asImmutable() + + private static final Integer MAX_SEARCH_RECORDS = 1000 + + /** + * Find a Part by partNumber. + * + * @param criteria containing partNumber + * + * @return part + */ + Map findPartByPartNumber(Map criteria) { + Map partData = dqx(criteria).executeOneFrom('dbo.vwPart', ['partNumber = :partNumber'])?.results[0] + Map partMetadata = dqx(criteria).executeOneFrom('dbo.vwPartMetadata', ['partNumber = :partNumber'])?.results[0] + if (partMetadata) { + partMetadata = transformMetadataClobToText(partMetadata) + mergeMetaDataWithPart(partData, partMetadata) + } else if (partData) { + mergeMetaDataAsNull(partData) + } + partData + } + + private void mergeMetaDataWithPart(Map partData, Map partMetaData) { + META_DATA.each { String metaData -> + partData."$metaData" = partMetaData."$metaData" + } + } + + private void mergeMetaDataAsNull(Map partData) { + partData.identity { + partSpecificText = null + internalNotes = null + relatedParts = null + relatedProducts = null + certificationUS = null + certificationEU = null + oemPartNumber = null + } + + } + + @SuppressWarnings('ParameterReassignment') + Map transformMetadataClobToText(Map partMetadata) { + partMetadata.collectEntries { k, v -> + if (v?.class?.simpleName == 'ClobImpl') { v = v.characterStream.text } + [k, v] + } + } + /** + * Find if a Part by partNumber exists. + * + * @param partNumber + * + * @return true if part exists + */ + boolean partWithPartNumberExists(String partNumber) { + sql().rows('SELECT * FROM vwPart WHERE partNumber = ?', [partNumber])[0] + } + + /** + * Find Parts matching the filter criteria. + * + * @param criteria containing filter criteria + * filters: + * partStatusCode - exact + * subComCode - exact/multiple + * productId - exact/multiple + * partNumber - like + * partDescr - like + * brandCode - like + * brandName - like + * vendorId - like + * vendorName - like + * vendorPartNumber - like + * q - like (without client supplied wildcards) on partNumber and partNumber + * + * @return Map with results containing parts that match the filter criteria + */ + Map findParts(Map criteria) { + criteria.sorting = criteria.sorting ?: 'partNumber+ASC' + criteria.columns = criteria.columns ?: '_search' + if (criteria.pageSize > MAX_SEARCH_RECORDS) { criteria.pageSize = MAX_SEARCH_RECORDS } + dqx(criteria).executeFrom('dbo.vwPart', searchClauses(criteria), COLUMN_SETS) + } + + Map findPartsByLocale(Map criteria) { + List clauses = ['partNumber = :partNumber', 'locale = :locale'] + Map partLocale = dqx(criteria).executeOneFrom('dbo.vwPartLocale', clauses)?.results[0] + if (partLocale) { + transformMetadataClobToText(partLocale) + } + } + + private List searchClauses(Map criteria) { + List clauses = [] + if (criteria.partStatusCode) { clauses << 'partStatusCode = :partStatusCode' } + Integer categoryId = criteria.categoryId + if (categoryId != null) { + if (categoryId == Part.UNASSIGNED_CATEGORY_ID) { clauses << 'categoryId IS NULL' } + else { clauses << 'categoryId = :categoryId' } + } + if (criteria.productId) { + if (criteria.productId == Part.UNASSIGNED_PRODUCT_ID) { clauses << 'primaryProductId IS NULL' } + else { + List productIdList = criteria.productId?.split(',') + String productIds = productIdList.collect { it }.join(',') + clauses << "primaryProductId IN ($productIds)" + } + } + if (criteria.subComCode) { + List subComCodeList = criteria.subComCode?.split(',') + String subComCodes = subComCodeList.collect { String unquoted -> "'$unquoted'" }.join(',') + clauses << "subComCode IN ($subComCodes)" + } + if (criteria.q) { + criteria.qLike = "%$criteria.q%" as String + clauses << '(partNumber LIKE :qLike OR partDescr LIKE :qLike)' + } + + clauses + wildcardClauses(criteria, SEARCHABLE_PROPERTIES) + } +} Index: grails-app/services/com/lemans/ds/product/CategoryProductManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/product/CategoryProductManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/product/CategoryProductManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,29 @@ +package com.lemans.ds.product + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class CategoryProductManagerService extends LemansManager { + + private final CategoryProductXmlGenerator categoryProductXmlGenerator = new CategoryProductXmlGenerator() + + def categoryService + + private static final String PART_ATTRIBUTE_PERSIST_SQL = + '''EXEC spUpdateProductCategory + @xml = ?, + @appSecurityUser = ? + ''' + + Map addOrRemoveCategoryProduct(Map values, String username) { + List errors = [] + if (categoryService.findCategoryById([catalogInstanceId: values.catalogInstanceId, id: values.categoryId])) { + String inputXML = categoryProductXmlGenerator.generateProductCategoryXml(values) + List results = sql().callWithAllRows(PART_ATTRIBUTE_PERSIST_SQL, [inputXML, username]) { } + errors.addAll(results[0]?.text) + } else { errors << 'Invalid category or catalog' } + [errors: errors] + } + +} Index: grails-app/services/com/lemans/ds/product/ProductAssociationManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/product/ProductAssociationManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/product/ProductAssociationManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,71 @@ +package com.lemans.ds.product + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class ProductAssociationManagerService extends LemansManager { + + def productAssociationService + + /** + * Adds ProductAssociation + * + * @param productId + * @param input + * @param username + * + * @return productAssociation + */ + ProductAssociation addProductAssociation(Integer productId, Map input, String username) { + ProductAssociation productAssociation = new ProductAssociation() + input.productId = productId + applyValuesToDomain(input, productAssociation) + productAssociation.validate() + validateProductAssociation(productAssociation) + saveOrDiscardDomain(productAssociation, username) + } + + /** + * Updates ProductAssociation + * + * @param partAssociationId + * @param input + * @param username + * + * @return productAssociation + */ + ProductAssociation updateProductAssociation(Integer id, Map input, String username) { + ProductAssociation productAssociation = productAssociationService.getAssociation(id) + if (productAssociation) { + applyValuesToDomain(input, productAssociation) + productAssociation.validate() + saveOrDiscardDomain(productAssociation, username) + } + } + + /** + * Deletes a productAssociation + * + * @param partAssociationId + * @param username + * + * @return productAssociation + */ + ProductAssociation deleteProductAssociation(Integer id, String username) { + ProductAssociation productAssociation = productAssociationService.getAssociation(id) + if (productAssociation) { + softDelete(productAssociation, username) + } + productAssociation + } + + private void validateProductAssociation(ProductAssociation productAssociation) { + if (ProductAssociation.findByProductIdAndRelatedProductIdAndAssociationTypeIdAndDateDeletedIsNull(productAssociation.productId, + productAssociation.relatedProductId, productAssociation.associationTypeId)) { + productAssociation.errors.reject('productAssociation.duplicate', + [productAssociation.productId, productAssociation.associationTypeId, productAssociation.relatedProductId] as Object[], + 'ProductAssociation for productId with associationTypeId and relatedProductId exists.') + } + } +} Index: grails-app/services/com/lemans/ds/product/ProductAssociationService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/product/ProductAssociationService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/product/ProductAssociationService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,55 @@ +package com.lemans.ds.product + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional +class ProductAssociationService extends LemansService { + + /** + * Find relatedProducts by productId + * + * @param criteria containing productId + * + * @return related products + */ + Map findRelatedProducts(Map criteria) { + criteria.sorting = criteria.sorting ?: 'productId+ASC' + dqx(criteria).executeFrom('dbo.vwProductAssociation', clauses(criteria)) + } + + /** + * Find referralProducts by related ProductId + * + * @param criteria contaning related productId + * + * @return referral Products + */ + Map findReferralProducts(Map criteria) { + criteria.sorting = criteria.sorting ?: 'relatedProductId+ASC' + dqx(criteria).executeFrom('dbo.vwProductAssociation', clauses(criteria)) + } + + /** + * Find ProductAssociation by ProductAssociationId + * + * @param criteria contaning productAssociationId + * + * @return productAssociation + */ + Map findProductAssociation(Map criteria) { + dqx(criteria).executeOneFrom('dbo.vwProductAssociation', clauses(criteria))?.results[0] + } + + private List clauses(Map criteria) { + List clauses = [] + if (criteria.productId) { clauses << 'productId = :productId' } + if (criteria.relatedProductId) { clauses << 'relatedProductId = :relatedProductId' } + if (criteria.productAssociationId) { clauses << 'productAssociationId = :productAssociationId' } + clauses + } + + ProductAssociation getAssociation(Integer id) { + ProductAssociation.findByIdAndDateDeletedIsNull(id) + } +} Index: grails-app/services/com/lemans/ds/product/ProductFeatureManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/product/ProductFeatureManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/product/ProductFeatureManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,150 @@ +package com.lemans.ds.product + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class ProductFeatureManagerService extends LemansManager { + + static final String PRODUCT_FEATURE_SQL = 'EXEC dbo.spUpdateProductFeature @xml = ?, @appSecurityUser = ?' + + static final String PRODUCT_FEATURE_MOVE_SQL = ''' +EXEC dbo.spMoveProductFeature + @sourceId = :productFeatureId, + @targetId = :targetId, + @sourceVersion = 0, + @targetVersion = 0, + @position = :position, + @appSecurityUser = :appSecurityUser +''' + + def messageSource + + def productService + + /** + * Create 1 or more ProductFeatures. + * + * @param values Map containing features and product info + * @param username + * + * @return Map containing messages which may contain errors and results with feature id's of Product Features created + */ + Map createProductFeatures(Map values, String username) { + Integer productId = values.productId + List features = values.features.collect { Map featureData -> + ProductFeature feature = new ProductFeature(featureData) + feature.productId = productId + feature + } + List messages = validateFeatures(features, values) + Map data + if (messages) { + data = [messages: messages, results: []] + } else { + data = saveFeatures(values, username) + } + data + } + + /** + * Updates a Product Feature. + * + * @param values Map containing catalog/product and feature info + * @param username + * + * @return ProductFeature which may contain errors or be null + */ + ProductFeature updateProductFeature(Map values, String username) { + ProductFeature feature = findProductFeature(values.productId, values.productFeatureId) + if (feature) { + feature.properties = values + saveOrDiscardDomain(feature, username) + } + feature + } + + /** + * Updates a Product Feature. + * + * @param values Map containing catalog/product and feature info + * @param username + * + * @return ProductFeatureLocale which may contain errors or be null + */ + ProductFeatureLocale updateProductFeatureWithLocale(Map values, String username) { + ProductFeatureLocale productFeatureLocale = ProductFeatureLocale. + findByProductFeatureIdAndLocaleAndDateDeletedIsNull(values.productFeatureId, values.locale) + if (!productFeatureLocale) { + ProductFeature productFeature = ProductFeature.findById(values.productFeatureId) + if (productFeature) { + productFeatureLocale = new ProductFeatureLocale([productFeatureId: productFeature.id]) + } else { + return + } + } + applyValuesToDomain(values, productFeatureLocale) + productFeatureLocale.validate() + saveOrDiscardDomain(productFeatureLocale, username) + + } + + /** + * Soft deletes a ProductFeature. + * + * @param productId + * @param productFeatureId + * @param username + * + * @return ProductFeature which may contain errors or be null + */ + ProductFeature deleteProductFeature(Integer productId, Integer productFeatureId, String username) { + ProductFeature feature = findProductFeature(productId, productFeatureId) + if (feature) { + softDelete(feature, username) + } + feature + } + + /** + * Changes the order of Features for a Product. + * + * @param values Map containing position and targetId + * @param username + * + * @return source ProductFeature + */ + ProductFeature moveProductFeature(Map values, String username) { + ProductFeature feature = findProductFeature(values.productId, values.productFeatureId) + if (feature) { + sql().execute(values + [appSecurityUser: username], PRODUCT_FEATURE_MOVE_SQL) + } + feature + } + + private ProductFeature findProductFeature(Integer productId, Integer productFeatureId) { + ProductFeature.findByProductIdAndIdAndDateDeletedIsNull(productId, productFeatureId) + } + + private Map saveFeatures(Map values, String username) { + String xml = new ProductFeaturesXmlTransformer().toXml(values) + List data = sql().callWithAllRows(PRODUCT_FEATURE_SQL, [xml, username]) { } + [messages: data[0], results: data[1]] + } + + private List validateFeatures(List features, Map values) { + List errors = [] + if (values.catalogInstanceId == null) { + errors << [type: 'error', text: 'Catalog is required'] + } + features.eachWithIndex { ProductFeature feature, int index -> + feature.validate() + if (feature.hasErrors()) { + errors += feature.errors.fieldErrors.collect { error -> + [type: 'error', field: error.field, text: messageSource.getMessage(error, Locale.default), index: index + 1] + } + } + } + errors + } +} Index: grails-app/services/com/lemans/ds/product/ProductFeatureService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/product/ProductFeatureService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/product/ProductFeatureService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,50 @@ +package com.lemans.ds.product + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional(readOnly = true) +class ProductFeatureService extends LemansService { + /** + * Finds a Feature for a Product by id. + * + * @param criteria Map containing featureId (productFeatureId) + * + * @return Map containing Product Feature info + */ + Map findProductFeatureById(Map criteria) { + dqx(criteria).executeOneFromLocale('dbo.vwProductFeature', product() + ['productFeatureId = :productFeatureId'])?.results[0] + } + + /** + * Finds a FeatureLocale for a Product Feature by Locale id. + * + * @param criteria Map containing featureLocaleId (productFeatureLocaleId) + * + * @return Map containing Product Feature Locale info + */ + Map findProductFeatureLocaleById(Map criteria) { + List clauses = ['productFeatureId = :productFeatureId', 'locale = :locale'] + dqx(criteria).executeOneFrom('dbo.vwProductFeatureLocale', clauses)?.results[0] + } + + /** + * Finds Features for a Product. Supports filtering by productId. + * + * @param criteria + * + * @return Map containing paginated Product Feature data + */ + Map findProductFeatures(Map criteria) { + criteria.sorting = criteria.sorting ?: 'sequence+ASC' + dqx(criteria).executeFromLocale('dbo.vwProductFeature', clauses(criteria)) + } + + private List product() { ['catalogInstanceId = :catalogInstanceId', 'productId = :productId'] } + + private List clauses(Map criteria) { + List clauses = [] + if (criteria.productId != null) { clauses.addAll(product()) } + clauses + } +} Index: grails-app/services/com/lemans/ds/product/ProductManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/product/ProductManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/product/ProductManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,104 @@ +package com.lemans.ds.product + +import com.lemans.ds.media.Media +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class ProductManagerService extends LemansManager { + + /** + * Creates a new Product. + * + * @param values + * @param username + * + * @return Product which may contain errors or be null + */ + Product createProduct(Map values, String username) { + Product product = new Product(values) + validateProduct(values, product) + saveOrDiscardDomain(product, username) + } + + + /** + * Updates a Product. + * + * @param values + * @param catalogInstanceId + * @param productId + * @param username + * + * @return Product which may contain errors or be null + */ + Product updateProduct(Map values, Integer catalogInstanceId, String username) { + Product product = findProduct(catalogInstanceId, values.productId) + if (product) { + applyValuesToDomain(values, product) + validateProduct(values, product) + saveOrDiscardDomain(product, username) + } + product + + } + + /** + * Updates a Product. + * + * @param values + * @param productLocaleId + * @param productId + * @param username + * + * @return ProductLocale which may contain errors or be null + */ + ProductLocale updateProductWithLocale(Map values, Integer catalogInstanceId, String username) { + ProductLocale productLocale = ProductLocale.findByProductIdAndLocaleAndDateDeletedIsNull(values.productId, values.locale) + if (!productLocale) { + Product product = findProduct(catalogInstanceId, values.productId) + if (product) { + productLocale = new ProductLocale([productId: product.id]) + } else { + return + } + } + applyValuesToDomain(values, productLocale) + productLocale.validate() + saveOrDiscardDomain(productLocale, username) + } + + /** + * Soft deletes a Product. + * + * @param catalogInstanceId + * @param productId + * @param username + * + * @return Product which may contain errors or be null + */ + Product deleteProduct(Integer catalogInstanceId, Integer productId, String username) { + Product product = findProduct(catalogInstanceId, productId) + if (product) { softDelete(product, username) } + product + } + + /** + * Finds Product data by catalogInstanceId AND productId + * @param catalogInstanceId + * @param productId + * + * @return the Product or null + */ + Product findProduct(Integer catalogInstanceId, Integer productId) { + Product.findByCatalogInstanceIdAndIdAndDateDeletedIsNull(catalogInstanceId, productId) + } + + + private void validateProduct(Map values, Product product) { + if (!product.hasErrors()) { product.validate() } + if (values.primaryMediaId != null && !findDomainObjectById(Media, values.primaryMediaId)) { + product.errors.rejectValue('primaryMediaId', 'invalid', "PrimaryMediaId with value [$values.primaryMediaId] does not exist") + } + } +} Index: grails-app/services/com/lemans/ds/product/ProductMerchandiseManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/product/ProductMerchandiseManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/product/ProductMerchandiseManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,38 @@ +package com.lemans.ds.product + +import com.lemans.ds.category.CategoryAttribute +import com.lemans.services.LemansManager +import grails.transaction.Transactional +import org.apache.commons.collections.CollectionUtils + +/** + * Created by MUmachi on 10/24/2017. + */ +@Transactional +class ProductMerchandiseManagerService extends LemansManager { + + private static final List FLAG_FIELDS = ['isHidden', 'isGroup', 'isDropdown', 'isKeyAttribute'] + + ProductCategoryAttribute updateProductCategoryAttribute(Map values, String username) { + ProductCategoryAttribute productCategoryAttribute = ProductCategoryAttribute. + findByProductIdAndCategoryAttributeIdAndDateDeletedIsNull( + values.productId, values.categoryAttributeId) + CategoryAttribute categoryAttribute = CategoryAttribute.findById(values.categoryAttributeId) + if (categoryAttribute) { + List keys = values.keySet() as String[] + if (CollectionUtils.containsAny(keys, FLAG_FIELDS)) { + FLAG_FIELDS.each { String flag -> + if (values."$flag" && categoryAttribute."$flag" == values."$flag") { + values."$flag" = null + } + } + } + } + if (!productCategoryAttribute) { + productCategoryAttribute = new ProductCategoryAttribute(values) + productCategoryAttribute.isSplit = values.isSplit ?: 0 + } + applyValuesToDomain(values, productCategoryAttribute) + saveOrDiscardDomain(productCategoryAttribute, username) + } +} Index: grails-app/services/com/lemans/ds/product/ProductMerchandiseService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/product/ProductMerchandiseService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/product/ProductMerchandiseService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,45 @@ +package com.lemans.ds.product + +import com.lemans.services.LemansService + +/** + * Created by MUmachi on 10/13/2017. + */ +class ProductMerchandiseService extends LemansService { + + private static final String PRODUCT_PART_DETAILS_SQL = 'SELECT dbo.fnGetProductPartAttributeDetailsJSON(?,?,?)' + + private static final String PRODUCT_DETAILS_SQL = 'SELECT dbo.fnGetProductAttributeDetailsJSON(?,?)' + + def productService + + + def findProductPartMerchandiseDetails(Map criteria) { + if (productService.findProductById(criteria)) { + queryForXmlClob(PRODUCT_PART_DETAILS_SQL, [criteria.productId, null, null]) + } else { + null + } + } + + Map findCategoryAttributeById(Map criteria) { + List clauses = ['productCategoryAttributeId = :productCategoryAttributeId'] + dqx(criteria).executeOneFrom('dbo.ProductCategoryAttribute', clauses)?.results[0] + } + + def findProductAttributeMerchandiseDetails(Map criteria) { + if (productService.findProductById(criteria)) { + queryForXmlClob(PRODUCT_DETAILS_SQL, [null, criteria.productId ]) + } else { + null + } + } + + def findProductAttributeMerchandiseLocaleDetails(Map criteria) { + if (productService.findProductById(criteria)) { + queryForXmlClob(PRODUCT_DETAILS_SQL, [criteria.locale, criteria.productId ]) + } else { + null + } + } +} Index: grails-app/services/com/lemans/ds/product/ProductPartManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/product/ProductPartManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/product/ProductPartManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,55 @@ +package com.lemans.ds.product + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class ProductPartManagerService extends LemansManager { + + private static final String PART_ATTRIBUTE_PERSIST_SQL = + '''EXEC spUpdateProductPart + @xml = ?, + @appSecurityUser = ? + ''' + private static final String FLAG_PRODUCT_UPDATE_SQL = + '''EXEC spUpdateFlagProductAttribute + @xmlParameters = ? + ''' + + Map removeSingleProductPartRelation(Map values, String userName) { + values.operation = 'DELETE' + bulkAddOrRemove(values, userName) + } + + Map addSingleProductPartRelation(Map values, String userName) { + values.operation = 'INSERT' + bulkAddOrRemove(values, userName) + } + + Map bulkAddOrRemove(Map input, String userName) { + List errors = [] + errors.addAll validateInput(input) + if (errors.isEmpty()) { + String inputXML = new ProductPartTransformer().generateProductPartXml(input) + List results = sql().callWithAllRows(PART_ATTRIBUTE_PERSIST_SQL, [inputXML, userName]) { } + if (results) { + processEntityFlag(input) + } + errors.addAll(results[0]?.text) + } + [errors: errors] + } + + List validateInput(Map input) { + List errors = [] + if (!input || (!input.partNumber && !input.partNumbers) || input.partNumbers?.isEmpty()) { + errors << 'No Part Selected' + } + errors + } + + void processEntityFlag(Map input) { + String inputXML = new ProductPartTransformer().generateProductFlagXml(input) + sql().execute(FLAG_PRODUCT_UPDATE_SQL, [inputXML]) { } + } +} Index: grails-app/services/com/lemans/ds/product/ProductPartService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/product/ProductPartService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/product/ProductPartService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,65 @@ +package com.lemans.ds.product + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional +class ProductPartService extends LemansService { + + def productService + + /** + * Finds a ProductPart by productId and PartNumber. + * + * @param criteria Map containing catalogInstanceId, productId and partNumber + * + * @return Map containing ProductPartInfo. + */ + Map findProductPartByComposite(Map criteria) { + List clause = [ + 'catalogInstanceId = :catalogInstanceId', + 'productId = :productId', + 'partNumber = :partNumber' + ] + dqx(criteria).executeOneFrom('dbo.vwProductPart', clause)?.results[0] + } + + + /** + * Finds a Products by PartNumber. + * + * @param criteria Map containing partNumber + * + * @return Map containing ProductPartInfo. + */ + Map findProductByPartNumber(Map criteria) { + List clauses = [] + List input = criteria.partNumber + List modifiedInput = input.collect { "\'$it\'" } + clauses << "partNumber IN (${modifiedInput.join(',')})" + Map results = dqx(criteria).executeFrom('dbo.vwProductPart', clauses) + results + } + + /** + * Finds product part relation by product and catalogInstance + * + * @param criteria containing catalogInstanceId and productId + * + * @return Map containing paginated data + */ + Map findProductParts(Map criteria) { + List clauses = [ + 'catalogInstanceId = :catalogInstanceId', + 'productId = :productId' + ] + criteria.sorting = criteria.sorting ?: 'sequence+ASC' + Map results = dqx(criteria).executeFrom('dbo.vwProductPart', clauses) + //check if catalogInstanceId and productId found + if (results.results.isEmpty()) { + Map product = productService.findProductById(criteria) + return product ? results : product + } + results + } +} Index: grails-app/services/com/lemans/ds/product/ProductService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/product/ProductService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/product/ProductService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,202 @@ +package com.lemans.ds.product + +import com.lemans.services.LemansService +import grails.transaction.Transactional +import groovy.json.JsonSlurper +import groovyx.gpars.GParsPool +import org.json.XML + +import java.sql.SQLException + + +@Transactional(readOnly = true) +class ProductService extends LemansService { + + def jobStatusManagerService + + private static final String SPLIT_VALIDATION = 'Exec spSplitProductValidation @productId = ?, @bulkValidation = ?' + /** + * Finds a Product by id. + * + * @param criteria Map containing catalogInstanceId and productId + * + * @return Map containing Product info + */ + Map findProductById(Map criteria) { + Map productData = dqx(criteria).executeOneFromLocale('dbo.vwProduct', catalog() + ['productId = :productId'])?.results[0] + if (productData && !localeExists(criteria)) { augmentProductsWithFlags([productData]) } + productData + } + + /** + * Finds a Product by id. + * + * @param criteria Map containing catalogInstanceId and productId + * + * @return Map containing Product info + */ + Map findProductLocaleById(Map criteria) { + List clauses = ['productId = :productId', 'locale = :locale'] + dqx(criteria).executeOneFrom('dbo.vwProductLocale', clauses)?.results[0] + } + + /** + * Finds Products for a Catalog. Supports filtering by brandCode and categoryId. + * + * @param criteria + * + * @return Map containing paginated data + */ + Map findProducts(Map criteria) { + boolean localeExists = localeExists(criteria) + updateSorting(criteria, localeExists) + if (localeExists) { + dqx(criteria).executeFromLocale('dbo.vwProduct', clauses(criteria)) + } else { + criteria.mode && criteria.flagId ? productsByFlagId(criteria) : products(criteria) + } + } + + private void updateSorting(Map criteria, boolean localeExists) { + updateSortingBasedOnQ(criteria, localeExists) + if (criteria.productName) { + criteria.sorting = criteria.sorting ?: 'case when productName ' + + 'like ' + "'$criteria.productName%' then 1 when productName like + '%$criteria.productName' " + + "then 3 when productName like + '%$criteria.productName%' then 2 end+ASC" + } + if (localeExists && criteria.productNameLocale) { + criteria.sorting = criteria.sorting ?: 'case when productNameLocale ' + + 'like ' + "'$criteria.productNameLocale%' then 1 when productNameLocale like + '%$criteria.productNameLocale' " + + "then 3 when productNameLocale like + '%$criteria.productNameLocale%' then 2 end+ASC" + } + if (criteria.productId) { + criteria.sorting = criteria.sorting ?: 'case when productId ' + + 'like ' + "'$criteria.productId%' then 1 when productId like + '%$criteria.productId' " + + "then 3 when productId like + '%$criteria.productId%' then 2 end+ASC" + } + criteria.sorting = criteria.sorting ?: localeExists ? 'productNameLocale+ASC' : 'productName+ASC' + } + + private void updateSortingBasedOnQ(Map criteria, boolean localeExists) { + if (criteria.q) { + if (localeExists) { + criteria.sorting = criteria.sorting ?: 'case when productNameLocale ' + + 'like ' + "'$criteria.q%' then 1 when productNameLocale like + '%$criteria.q' " + + "then 3 when productNameLocale like + '%$criteria.q%' then 2 end+ASC" + } else { + criteria.sorting = criteria.sorting ?: 'case when productName ' + + 'like ' + "'$criteria.q%' then 1 when productName like + '%$criteria.q' " + + "then 3 when productName like + '%$criteria.q%' then 2 end+ASC" + } + } + } + + private Map products(Map criteria) { + Map products = dqx(criteria).executeFrom('dbo.vwProduct', clauses(criteria)) + if (criteria.mode) { augmentProductsWithFlags(products.results) } + products + } + + private Map productsByFlagId(Map criteria) { + Map products = dqx(criteria).executeFromWithOutAlias( + '''vwProduct + INNER JOIN vwFlagValue + ON vwProduct.productId = vwFlagValue.entityId + ''', + clauses(criteria) + ["vwFlagValue.entityClass = 'Product'", 'vwFlagValue.flagId = :flagId'] + ) + augmentProductsWithFlags(products.results) + products + } + + private List catalog() { ['catalogInstanceId = :catalogInstanceId'] } + + private List clauses(Map criteria) { + List clauses = [] + if (criteria.brandCode != null) { clauses << 'brandCode = :brandCode' } + if (criteria.brandId != null) { clauses << 'brandId = :brandId' } + if (criteria.isDigiActive != null) { clauses << 'isDigiActive = :isDigiActive' } + if (criteria.categoryId != null) { clauses << "categoryId IN (${criteria.categoryId.join(',')})" } + if (criteria.productId != null) { clauses << 'productId = :productId' } + clauses.addAll wildcardClauses(criteria, [localeName(criteria, 'productName')]) + if (criteria.q) { + clauses.addAll qClauses(criteria) + } + clauses + } + + private List qClauses(criteria) { + List clauses = [] + if (criteria.q) { + expandQ(criteria) + if (localeExists(criteria)) { + String clause = ''' + productId LIKE :beginningWithQ + OR productId LIKE :containingQ + OR productNameLocale LIKE :beginningWithQ + OR productNameLocale LIKE :containingQ''' + clauses << clause + } else { + String clause = ''' + productId LIKE :beginningWithQ + OR productId LIKE :containingQ + OR productName LIKE :beginningWithQ + OR productName LIKE :containingQ''' + clauses << clause + } + } + clauses + } + + private expandQ(criteria) { + criteria.with { + beginningWithQ = "$q%".toString() + containingQ = "%$q%".toString() + } + } + + private void augmentProductsWithFlags(List products) { + if (products) { + String query = """ + SELECT * FROM dbo.vwflagValue fv + WHERE fv.entityClass = 'Product' + AND fv.entityId IN (${products*.productId.join(',')}) + """ + Map flagsByProductId = sql().rows(query).groupBy { it.entityId } + products.each { it.flag = flagsByProductId[it.productId.toString()] } + } + } + + private String localeName(Map criteria, String name) { + name + (localeExists(criteria) ? 'Locale' : '') + } + + private boolean localeExists(Map criteria) { + criteria.locale && criteria.locale != Locale.default.toString() + } + + Map splitValidationForProduct(Integer productId) { + String xmlResults = queryForXmlClob(SPLIT_VALIDATION, [productId, 0]) + if (xmlResults) { + String validatedResults = XML.toJSONObject(xmlResults) + [results: new JsonSlurper().parseText(validatedResults)] + } else { + [results: [:]] + } + } + + void bulkSplitValidation() { + try { + GParsPool.withPool(5) { + sql().rows('SELECT productId FROM product WITH(NOLOCK) WHERE dateDeleted IS NULL')?.productId?.eachParallel { Integer productId -> + Product.withNewTransaction { + sql().execute(SPLIT_VALIDATION, [productId, 1]) + } + } + } + } catch (SQLException e) { + log.error e.message + jobStatusManagerService.updateEndDate() + } + } + } Index: grails-app/services/com/lemans/ds/publicationcategory/ProductPublicationCategoryManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/publicationcategory/ProductPublicationCategoryManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/publicationcategory/ProductPublicationCategoryManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,51 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class ProductPublicationCategoryManagerService extends LemansManager { + + def productPublicationCategoryService + + /** + * Create a new ProductPublicationCategory + * + * @param input + * @param publicationCategoryId + * @param username + * + * @return ProductPublicationCategory + */ + ProductPublicationCategory add(Map input, Integer publicationCategoryId, String username) { + input.categoryId = publicationCategoryId + ProductPublicationCategory productPublicationCategory = productPublicationCategoryService.findProductPublicationCategory(publicationCategoryId, input.productId) + if (productPublicationCategory) { + productPublicationCategory.errors.reject('productPublicationCategory.duplicate', [productPublicationCategory.productId] as Object[], + 'Product has already been assigned') + } else { + productPublicationCategory = new ProductPublicationCategory() + applyValuesToDomain(input, productPublicationCategory) + productPublicationCategory.validate() + saveOrDiscardDomain(productPublicationCategory, username) + } + productPublicationCategory + } + + /** + * Delete a productPublicationCategory by productId and publicationCategoryId + * + * @param input + * @param publicationCategoryId + * @param username + * + * @return ProductPublicationCategory + */ + ProductPublicationCategory deleteProductPublicationCategory(Integer categoryId, Integer productId, String username) { + ProductPublicationCategory productPublicationCategory = productPublicationCategoryService.findProductPublicationCategory(categoryId, productId) + if (productPublicationCategory) { + softDelete(productPublicationCategory, username) + } + productPublicationCategory + } +} Index: grails-app/services/com/lemans/ds/publicationcategory/ProductPublicationCategoryService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/publicationcategory/ProductPublicationCategoryService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/publicationcategory/ProductPublicationCategoryService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,47 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional +class ProductPublicationCategoryService extends LemansService { + + /** + * Find all ProductPublicationCategories by categoryId + * + * @param criteria + * + * @return ProductPublicationCategories data + */ + Map productPublicationCategoriesByCategoryId(Map criteria) { + criteria.sorting = criteria.sorting ?: 'categoryId+ASC' + dqx(criteria).executeFrom('vwProductPublicationCategory', ['categoryId = :categoryId', 'isDigiActive = :isDigiActive']) + } + + /** + * Find productPublicationCategory by id + * + * @param id (productPublicationCategoryId) + * + * @return ProductPublicationCategory data + */ + Map productPublicationCategory(Integer id) { + dqx([id: id]).executeOneFrom('vwProductPublicationCategory', ['productPublicationCategoryId = :id'])?.results[0] + } + + /** + * Find productPublicationCategories by productId + * + * @param criteria + * + * @return ProductPublicationCategories data + */ + Map productPublicationCategoriesByProductId(Map criteria) { + criteria.sorting = criteria.sorting ?: 'categoryId+ASC' + dqx(criteria).executeFrom('vwProductPublicationCategory', ['productId = :productId', 'isDigiActive = :isDigiActive']) + } + + ProductPublicationCategory findProductPublicationCategory(Integer categoryId, Integer productId) { + ProductPublicationCategory.findByCategoryIdAndProductIdAndDateDeletedIsNull(categoryId, productId) + } +} Index: grails-app/services/com/lemans/ds/publicationcategory/PublicationCategoryAttributeManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/publicationcategory/PublicationCategoryAttributeManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/publicationcategory/PublicationCategoryAttributeManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,100 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.FeatureToggles +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class PublicationCategoryAttributeManagerService extends LemansManager { + + private static final int FORCED_OPT_LOCKING_VERSION = -1 + + private static final String SEQUENCING = ''' + EXEC dbo.spMovePublicationCategoryAttribute + @sourceId = :id, + @targetId = :targetId, + @sourceVersion = :sourceVersion, + @targetVersion = :targetVersion, + @position = :position, + @appSecurityUser = :appSecurityUser + ''' + /** + * Add PublicationCategoryAttribute + * @param input + * @param username + * @return PublicationCategoryAttribute Object + */ + PublicationCategoryAttribute addAttribute(Map input, String username) { + PublicationCategoryAttribute publicationCategoryAttribute = new PublicationCategoryAttribute() + applyValuesToDomain(input, publicationCategoryAttribute) + publicationCategoryAttribute.validate() + validatePublicationCategory(input.publicationCategoryId, publicationCategoryAttribute) + validatePublicationCategoryAttribute(input.publicationCategoryId, input.attributeNameId, publicationCategoryAttribute) + saveOrDiscardDomain(publicationCategoryAttribute, username) + flushAndSequence(publicationCategoryAttribute, username) + publicationCategoryAttribute + } + + /** + * Delete PublicationCategoryAttribute + * @param input + * @param username + * @return Soft deleted PublicationCategoryAttribute Object + */ + PublicationCategoryAttribute deleteAttribute(Integer publicationCategoryId, Integer attributeNameId, String username) { + PublicationCategoryAttribute publicationCategoryAttribute = findPublicationCategoryAttribute(publicationCategoryId, attributeNameId) + if (publicationCategoryAttribute) { + softDelete(publicationCategoryAttribute, username) + } + publicationCategoryAttribute + } + + /** + * Sequence reset of publicationCategoryAttribute + * @param input + * @param username + * @return PublicationCategoryAttribute Object + */ + PublicationCategoryAttribute moveAttribute(Map values, String username) { + PublicationCategoryAttribute source = findPublicationCategoryAttribute(values.publicationCategoryId, values.attributeNameId) + values.attributeNameId = values.targetId + PublicationCategoryAttribute target = findPublicationCategoryAttribute(values.publicationCategoryId, values.attributeNameId) + if (source && target) { + if (FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE) { + checkForStaleObject(PublicationCategoryAttribute, 'PublicationCategoryAttribute', values.sourceVersion ?: FORCED_OPT_LOCKING_VERSION) + } + values.id = source.id + values.targetId = target.id + sequence(values + [appSecurityUser: username]) + source + } + } + + private validatePublicationCategory(Integer publicationCategoryId, PublicationCategoryAttribute publicationCategoryAttribute) { + PublicationCategory publicationCategory = PublicationCategory.findByIdAndDateDeletedIsNull(publicationCategoryId) + if (!publicationCategory) { + publicationCategoryAttribute.errors.reject('publicationCategory.invalid', 'Invalid publication Category') + } + } + + private validatePublicationCategoryAttribute(Integer publicationCategoryId, Integer attributeNameId, PublicationCategoryAttribute publicationCategoryAttribute) { + if (findPublicationCategoryAttribute(publicationCategoryId, attributeNameId)) { + publicationCategoryAttribute.errors.reject('publicationCategoryAttribute.duplicate', 'Duplicate publicationCategoryAttribute') + } + } + + private PublicationCategoryAttribute findPublicationCategoryAttribute(Integer publicationCategoryId, Integer attributeNameId) { + PublicationCategoryAttribute.findByPublicationCategoryIdAndAttributeNameIdAndDateDeletedIsNull(publicationCategoryId, attributeNameId) + } + + private flushAndSequence(PublicationCategoryAttribute publicationCategoryAttribute, String username) { + if (!publicationCategoryAttribute.hasErrors()) { + publicationCategoryAttribute.save(flush: true) + sequence([id: publicationCategoryAttribute.id, appSecurityUser: username]) + } + } + + private void sequence(Map input) { + sql().execute(input, SEQUENCING) + } +} Index: grails-app/services/com/lemans/ds/publicationcategory/PublicationCategoryAttributeService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/publicationcategory/PublicationCategoryAttributeService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/publicationcategory/PublicationCategoryAttributeService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,50 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional +class PublicationCategoryAttributeService extends LemansService { + + /** + * Find all publicationCategoryAttributes + * @param id (publicationCategoryId) + * @return Map of publicationCategoryAttributes + */ + Map findAllAttributes(Map criteria) { + criteria.sorting = criteria.sorting ?: 'publicationCategoryId+ASC' + dqx(criteria).executeFrom('vwPublicationCategoryAttribute', ['publicationCategoryId = :publicationCategoryId']) + } + + /** + * Find possible attributeOptions for a publicationCategory + * @param id (PublicationCategoryId) + * @return Map of results(AttributeNameIds) + */ + Map attributeOptions(Integer id) { + List data = sql().callWithAllRows('EXEC dbo.spGetPublicationCategoryAttributes @categoryId = ?', [id]) { } + [results: data[0], totalRecords: data[1][0].TotalRecords] + } + + /** + * Find publicationCategoryId + * @param publicationCategoryId + * @param attributeNameId + * @return Map of publicationCategoryId + */ + Map findPublicationCategoryAttribute(Integer publicationCategoryId, Integer attributeNameId) { + List clauses = ['publicationCategoryId = :publicationCategoryId', 'attributeNameId = :attributeNameId'] + Map criteria = [publicationCategoryId: publicationCategoryId, attributeNameId: attributeNameId] + dqx(criteria).executeOneFrom('vwPublicationCategoryAttribute', clauses)?.results[0] + } + + /** + * Find publicationCategoryAttribute + * @param id (publicationCategoryAttributeId) + * @return Map of publicationCategoryAttribute + */ + Map findById(Integer id) { + dqx([publicationCategoryAttributeId: id]).executeOneFrom('vwPublicationCategoryAttribute', + ['publicationCategoryAttributeId = :publicationCategoryAttributeId'])?.results[0] + } +} Index: grails-app/services/com/lemans/ds/publicationcategory/PublicationCategoryManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/publicationcategory/PublicationCategoryManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/publicationcategory/PublicationCategoryManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,189 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.FeatureToggles +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class PublicationCategoryManagerService extends LemansManager { + + private static final int FORCED_OPT_LOCKING_VERSION = -1 + + /** + * Create a Publication Category + * + * @param input + * @param username + * + * @return Publication Category + */ + PublicationCategory createCategory(Map input, String username) { + PublicationCategory publicationCategory = new PublicationCategory() + applyValuesToDomain(input, publicationCategory) + publicationCategory.sequence = PublicationCategory.MAX_DIRECT_CHILDREN + publicationCategory.validate() + saveOrDiscardDomain(publicationCategory, username) + flushAndSequence(publicationCategory) + publicationCategory + } + + /** + * Update a Publication Category + * + * @param input + * @param id + * @param username + * + * @return Publication Category + */ + PublicationCategory updateCategory(Map input, Integer id, String username) { + PublicationCategory publicationCategory = findCategory(id) + if (publicationCategory) { + applyValuesToDomain(input, publicationCategory) + if (FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE) { + checkForStaleObject(publicationCategory, 'PublicationCategory', input.version ?: FORCED_OPT_LOCKING_VERSION) + } + saveOrDiscardDomain(publicationCategory, username) + flushAndSequence(publicationCategory) + } + publicationCategory + } + + /** + * Move the position of a Publication Category + * + * @param input + * @param id (categoryId) + * @param username + * + * @return Publication Category + */ + PublicationCategory movePublicationCategory(Map input, Integer id, String username) { + PublicationCategory publicationCategory = findCategory(id) + if (publicationCategory) { + if (FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE) { + checkForStaleObject(publicationCategory, 'PublicationCategory', input.version ?: FORCED_OPT_LOCKING_VERSION) + } + PublicationCategory targetPublicationCategory = findCategory(input.targetId) + if (!targetPublicationCategory) { return null } + checkTreeConstraints(publicationCategory, targetPublicationCategory.id) + if (input.position == 'INSIDE') { + if (!publicationCategory.hasErrors()) { + publicationCategory.parentCategoryId = input.targetId + publicationCategory.sequence = 1 + } + } else { + if (!publicationCategory.hasErrors()) { + publicationCategory.parentCategoryId = targetPublicationCategory.parentCategoryId + publicationCategory.sequence = input.position == 'AFTER' ? targetPublicationCategory.sequence + 1 : targetPublicationCategory.sequence + } + } + saveOrDiscardDomain(publicationCategory, username) + flushAndSequence(publicationCategory) + publicationCategory + } + } + + /** + * Delete a publication Category + * + * @param id (categoryId) + * @param version + * @param username + * + * @return Publication Category + */ + PublicationCategory deleteCategory(Integer id, Integer version, String username) { + PublicationCategory publicationCategory = findCategory(id) + if (publicationCategory) { + if (FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE) { + checkForStaleObject(publicationCategory, 'publicationCategory', version ?: FORCED_OPT_LOCKING_VERSION) + } + if (children(publicationCategory)) { + publicationCategory.errors.reject('publicationCategory.notDeletable', ['PublicationCategory'] as Object[], + 'Publication Category may not be deleted') + } else { + softDelete(publicationCategory, username) + flushAndSequence(publicationCategory) + } + } + publicationCategory + } + + void sequence(PublicationCategory publicationCategory) { + PublicationCategory.withTransaction { + sql().execute(""" + UPDATE publicationcategory + SET sequence = ${publicationCategory.sequence + 1} + WHERE + ISNULL(parentCategoryId, -1) = ISNULL(${publicationCategory.parentCategoryId},-1) AND + sequence >= ${publicationCategory.sequence} AND + categoryId <> ${publicationCategory.id} AND + dateDeleted is NULL + """) + resetSequence() + } + } + + void resetSequence() { + sql().execute(''' + UPDATE c + SET sequence = a.sequence + FROM publicationCategory c + INNER JOIN ( + SELECT c.categoryId, sequence = ROW_NUMBER() OVER (PARTITION BY parentCategoryId ORDER BY sequence, categoryName) + FROM publicationCategory c + WHERE c.dateDeleted IS NULL + ) a + ON a.categoryId = c.categoryId + AND a.sequence <> c.sequence + WHERE c.dateDeleted IS NULL + ''') + } + + private PublicationCategory findCategory(Integer id) { + PublicationCategory.findByIdAndDateDeletedIsNull(id) + } + + private List children(PublicationCategory publicationCategory) { + PublicationCategory.findAllByParentCategoryIdAndDateDeletedIsNull(publicationCategory.id) + } + + private void flushAndSequence(PublicationCategory publicationCategory) { + if (publicationCategory && !publicationCategory.hasErrors()) { + publicationCategory.save(flush: true) + sequence(publicationCategory) + } + } + + private void checkTreeConstraints(PublicationCategory publicationCategory, Integer targetId) { + List parentIds = sql().rows(""" + WITH ParentsCTE + AS ( + SELECT c1.categoryId + ,c1.parentCategoryId + FROM [PartsSource_DS].[dbo].PublicationCategory c1 + WHERE c1.dateDeleted IS NULL + AND c1.categoryId = $targetId + + UNION ALL + + SELECT c2.categoryId + ,c2.parentCategoryId + FROM [PartsSource_DS].[dbo].PublicationCategory c2 + INNER JOIN ParentsCTE c ON c.parentCategoryId = c2.categoryId + WHERE c2.dateDeleted IS NULL + ) + SELECT parentCategoryId + FROM ParentsCTE + WHERE parentCategoryId IS NOT NULL; + """)*.parentCategoryId + if (publicationCategory.id in parentIds) { rejectCycle(publicationCategory) } + } + + private rejectCycle(PublicationCategory publicationCategory) { + publicationCategory.errors.reject('publicationCategory.cyclic', ['PublicationCategory'] as Object[], + 'PublicationCategory may not be moved to create a cycle') + + } +} Index: grails-app/services/com/lemans/ds/publicationcategory/PublicationCategoryService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/publicationcategory/PublicationCategoryService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/publicationcategory/PublicationCategoryService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,31 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional +class PublicationCategoryService extends LemansService { + + /** + * Find Publication Categories + * + * @param criteria + * + * @return Publication Categories data + */ + Map findPublicationCategories(Map criteria) { + criteria.sorting = criteria.sorting ?: 'categoryId+ASC' + dqx(criteria).executeFrom('dbo.vwPublicationCategory') + } + + /** + * Find Publication Category by id + * + * @param id (categoryId) + * + * @return Publication Category data + */ + Map findPublicationCategoryById(Integer id) { + dqx([id: id]).executeOneFrom('dbo.vwPublicationCategory', ['categoryId = :id'])?.results[0] + } +} Index: grails-app/services/com/lemans/ds/rabbit/RabbitMessageService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/rabbit/RabbitMessageService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/rabbit/RabbitMessageService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,18 @@ +package com.lemans.ds.rabbit + +class RabbitMessageService { + + def rabbitMessagePublisher + + def usToEuSyncExchange + + def usToEuSyncBinding + + void publishToUsTOEuQueue(RequestPayload payload) { + rabbitMessagePublisher.send { + exchange = usToEuSyncExchange + routingKey = usToEuSyncBinding + body = [requestType: payload.requestType, id: payload.id, body: payload.body] + } + } +} Index: grails-app/services/com/lemans/ds/rabbit/RequestPayload.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/rabbit/RequestPayload.groovy (revision 0) +++ grails-app/services/com/lemans/ds/rabbit/RequestPayload.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,8 @@ +package com.lemans.ds.rabbit + +class RequestPayload { + + QueueRequestType requestType + Integer id + Object body +} Index: grails-app/services/com/lemans/ds/search/BrandSearchService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/search/BrandSearchService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/search/BrandSearchService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,46 @@ +package com.lemans.ds.search + +import com.lemans.services.LemansService +import grails.transaction.Transactional + +@Transactional(readOnly = true) +class BrandSearchService extends LemansService { + + private static final String BRAND_SELECT = 'SELECT x.brandId, x.brandCode, RTRIM(x.brandName) AS brandName' + /** + * Searches for Brands supporting * wildcards. + * + * @param criteria containing brandCode or brandName + * + * @return Map with List of results + */ + Map searchBrands(Map criteria) { + if (criteria.query) { + criteria.q = formatWildCardSearch(criteria.query) + } + Map results = dqx(criteria).executeSelectFrom(BRAND_SELECT, 'FROM dbo.Brand x', clauses(criteria)) + results + } + + private String formatWildCardSearch(String query) { + String q = query + if (q) { + if (q.contains('*')) { + q.replace('*', '%') + } else { + "${q}%" + } + } + } + + private List clauses(Map criteria) { + List clauses = [] + + if (criteria.q) { + clauses << '(x.brandCode LIKE :q OR x.brandName LIKE :q)' + } + + clauses + + } +} Index: grails-app/services/com/lemans/ds/search/GenericSearchService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/search/GenericSearchService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/search/GenericSearchService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,87 @@ +package com.lemans.ds.search + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +@Transactional +class GenericSearchService extends LemansManager { + + private static final String FILTER_SQL = 'EXEC dbo.spResponsiveSearchFilters @filterType = ?, @selectedParameters = ?' + + private static final String FILTER_OPTIONS_SQL = 'EXEC dbo.spResponsiveSearchFilters_V1 @filterType = ?, @selectedParameters = ?' + + private static final String FILTER_RESULTS_SQL = 'EXEC dbo.spResponsiveSearchResults @filterType = ?, @selectedParameters = ?, ' + + '@offset = ?, @pagesize = ?, @sortBy = ?, @mode = ?' + + private final FilterXmlGenerator filterXmlGenerator = new FilterXmlGenerator() + + /** + * Returns Filters and their values. + * + * @param searchType + * @param filterType + * @param criteria Map + * + * @return Map containing results and totalRecords + */ + Map filterOptions(String searchType, String filterType, Map criteria) { + String xml = filterXmlGenerator.generateXml(searchType, filterType, criteria) + List data = sql().callWithAllRows(FILTER_OPTIONS_SQL, [filterType, xml]) { } + [results: data ? data[0] : [], totalRecords: data ? data[0].size() : 0] + } + + /** + * Returns Filters and their values. + * @param searchType + * @param filterType + * @param criteria Map + * @return XML containing filter results + */ + String filters(String searchType, String filterType, Map criteria) { + String xml = filterXmlGenerator.generateXml(searchType, filterType, criteria) + queryForXml(FILTER_SQL, [filterType, xml]) + } + + /** + * Returns Results with totalRecords. + * + * @param searchType + * + * @param filterType + * + * @param criteria Map + * + * @return Map containing results and totalRecords + */ + Map searchResults(String searchType, String filterType, Map criteria) { + List data = genericSearchResults(searchType, filterType, criteria) + [results: data ? data[0] : [], totalRecords: data ? (data[1][0]?.totalRecords ?: 0) : 0] + } + + /** + * Returns Results with totalRecords. + * + * @param searchType + * + * @param filterType + * + * @param criteria Map + * + * @return Map containing resultXml and totalRecords + */ + Map searchResultsByMode(String searchType, String filterType, Map criteria) { + List data = genericSearchResults(searchType, filterType, criteria) + Integer totalRecords = data ? data[1][0].totalRecords : 0 + [resultXML: totalRecords ? data[0][0][0].characterStream.text : '', totalRecords: totalRecords] + } + + private List genericSearchResults(String searchType, String filterType, Map criteria) { + List parameters = parameters(searchType, filterType, criteria) + sql().callWithAllRows(FILTER_RESULTS_SQL, parameters) { } + } + + private List parameters(String searchType, String filterType, Map criteria) { + String xml = filterXmlGenerator.generateXml(searchType, filterType, criteria) + [filterType, xml, criteria.offset ?: 0, criteria.pageSize ?: 25, criteria.sorting ?: null, criteria.mode] + } +} Index: grails-app/services/com/lemans/ds/splitvalidation/JobStatusManagerService.groovy =================================================================== diff -u --- grails-app/services/com/lemans/ds/splitvalidation/JobStatusManagerService.groovy (revision 0) +++ grails-app/services/com/lemans/ds/splitvalidation/JobStatusManagerService.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,49 @@ +package com.lemans.ds.splitvalidation + +import com.lemans.services.LemansManager +import grails.transaction.Transactional + +import java.sql.Timestamp + +@Transactional +class JobStatusManagerService extends LemansManager { + + def productService + + def execute() { + if (jobStatus()) { + updateStartDate() + productService.bulkSplitValidation() + updateEndDate() + } + } + + Map jobStatus() { + JobStatus.withNewTransaction { + Map jobStatus = sql().firstRow('SELECT * FROM jobStatus WITH(NOLOCK) WHERE jobStatusId = 1 AND dateDeleted IS NULL') + jobStatus + } + } + + void updateStartDate() { + JobStatus.withNewTransaction { + sql().execute(""" + UPDATE jobStatus SET + startDate = ${ new Timestamp(new Date().time) }, + lastUpdated = ${ new Timestamp(new Date().time) } + WHERE jobStatusId = 1 + """) + } + } + + void updateEndDate() { + JobStatus.withNewTransaction { + sql().execute(""" + UPDATE jobStatus SET + endDate = ${ new Timestamp(new Date().time) }, + lastUpdated = ${ new Timestamp(new Date().time) } + WHERE jobStatusId = 1 + """) + } + } +} Index: grails-app/views/index.gson =================================================================== diff -u --- grails-app/views/index.gson (revision 0) +++ grails-app/views/index.gson (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,3 @@ +json { + results 'ds-service' +} \ No newline at end of file Index: src/integration-test/groovy/com/lemans/common/SubComCodeFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/common/SubComCodeFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/common/SubComCodeFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,51 @@ +package com.lemans.common + +import com.lemans.ds.testing.DsFuncSpec + +class SubComCodeFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'subComCode' } + + final static int ALL_COLUMNS_SIZE = 13 + + def 'can NOT find a SubComCode that does not exist'() { + given: + path('-1') + notFound() + + when: + get() + + then: + !payload + } + + def 'can find a SubComCode'() { + given: + String subComCode = '0101' + path(subComCode) + ok() + + when: + get() + + then: + payload.results.subComCode == subComCode + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can find all SubComCodes'() { + given: + path() + ok() + + when: + get() + + then: + payload.results.subComCode.every { it != null } + payload.results[0].size() == ALL_COLUMNS_SIZE + payload.results.size() > 350 + } +} Index: src/integration-test/groovy/com/lemans/ds/CatalogTestFixture.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/CatalogTestFixture.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/CatalogTestFixture.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,10 @@ +package com.lemans.ds + +import com.lemans.ds.product.Product + +trait CatalogTestFixture { + + Integer catalogInstanceId = Product.VIRTUAL_CATALOG_ID + + Map catalog = [catalog: catalogInstanceId] +} Index: src/integration-test/groovy/com/lemans/ds/attribute/AttributeCategoryFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/attribute/AttributeCategoryFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/attribute/AttributeCategoryFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,85 @@ +package com.lemans.ds.attribute + +import com.lemans.ds.testing.DsFuncSpec + +class AttributeCategoryFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { '' } + + final static int ALL_COLUMNS_SIZE = 22 + + def 'can get all categories filtered by attribute Name'() { + given: + path('attributeName/1081/category') + ok() + + when: + get() + + then: + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + def 'can get all categories filtered by attribute Name and category'() { + given: + path('attributeName/1081/category') + queryParams.categoryId = 592 + ok() + + when: + get() + + then: + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find category attributes with out attributeNameId'() { + given: + path('attributeName/null/category') + invalid() + + when: + get() + + then: + payload.messages[0].text == 'attributeNameId is required' + } + + def 'can get all categories filtered by attribute Value'() { + given: + path('attributeValue/101059/category') + ok() + + when: + get() + + then: + payload.results[0].size() == 20 + } + + def 'can find all categories filtered by attribute Value and category'() { + given: + path('attributeValue/101059/category') + queryParams.categoryId = 6917 + ok() + + when: + get() + + then: + payload.results[0].size() == 20 + } + + def 'can NOT find category attributes with out attributeValueId'() { + given: + path('attributeValue/null/category') + invalid() + + when: + get() + + then: + payload.messages[0].text == 'attributeValueId is required' + } +} Index: src/integration-test/groovy/com/lemans/ds/attribute/AttributeNameFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/attribute/AttributeNameFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/attribute/AttributeNameFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,202 @@ +package com.lemans.ds.attribute + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared +import spock.lang.Stepwise + +@Stepwise +class AttributeNameFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'attributeName' } + + final static int ALL_COLUMNS_SIZE = 13 + + @Shared + Integer currentAttributeNameId + + def 'can not create attributeName without blank attributeName field'() { + given: + String json = '{ "attributeName": "" }' + path() + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'attributeName' + text.contains 'attributeName is required' + } + payload.messages.size() == 1 + } + + def 'can not create duplicate attributeName'() { + given: + String attributeName = 'Size' + String json = """{ "attributeName": "$attributeName" }""" + path() + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + code == 'DUPLICATE_KEY' + } + payload.messages.size() == 1 + } + + def 'can create valid attributeName '() { + given: + String key = 'zzz_' + new Date() + String attributeName = "$key TestCreate" + String json = """{ "attributeName": "$attributeName" }""" + path() + ok() + + when: + post(json) + currentAttributeNameId = payload.results.attributeNameId + then: + payload.results.attributeName == attributeName + payload.results.size() == ALL_COLUMNS_SIZE + + } + + + def 'can NOT update attributeName with invalid attributeNameId '() { + given: + Integer attributeNameId = -1 + String key = 'zzz_' + new Date() + String attributeName = "$key updated" + String json = """{ "attributeName": "$attributeName" }""" + path(attributeNameId) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can NOT update attributeName with invalid attributeName'() { + given: + String attributeName = 'This is long text, This is long text with lots of characters, so its long text with more than 100 text.' + String json = """{ "attributeName": "$attributeName" }""" + path(currentAttributeNameId) + invalid() + + when: + put(json) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'attributeName' + text.contains 'exceeds the maximum size' + } + payload.messages.size() == 1 + } + + def 'can update attributeName'() { + given: + String key = 'zzz_' + new Date() + String attributeName = "$key updated" + String json = """{ "attributeName": "$attributeName" }""" + path(currentAttributeNameId) + ok() + + when: + put(json) + + then: + payload.results.attributeName == attributeName + payload.results.attributeNameId == currentAttributeNameId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find AttributeName for invalid attributeNameId'() { + given: + path(-1) + notFound() + + when: + get() + + then: + !payload + } + + def 'can get all AttributeNames'() { + given: + path() + ok() + + when: + get() + + then: + apiPaginated() > 10 + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + def 'can find a AttributeName'() { + given: + path(currentAttributeNameId) + ok() + + when: + get() + + then: + payload.results.attributeNameId == currentAttributeNameId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can not delete a AttributeName if related to Category or Part'() { + given: + path(1089) + invalid() + + when: + delete() + + then: + with(payload.messages[0]) { + type == 'error' + text.contains 'Cannot delete AttributeName when assigned to Category or Part' + } + payload.messages.size() == 1 + } + + def 'can Not delete a AttributeName for invalid Id'() { + given: + path(-1) + notFound() + + when: + delete() + + then: + !payload + } + + def 'can delete a AttributeName'() { + given: + path(currentAttributeNameId) + ok() + + when: + delete() + + then: + !payload + } + +} Index: src/integration-test/groovy/com/lemans/ds/attribute/AttributeNameLocaleFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/attribute/AttributeNameLocaleFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/attribute/AttributeNameLocaleFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,346 @@ +package com.lemans.ds.attribute + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Ignore +import spock.lang.Stepwise + +@Stepwise +class AttributeNameLocaleFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'attributeName' } + + final static int ALL_COLUMNS_SIZE = 16 + + + Integer validAttributeNameId = 1024 + + Integer invalidAttributeNameId = 1183 + + String invalidLocale = 'xy' + + def 'can create attributeNameLocale with attributeName id that exist'() { + given: + String attributeName = 'updated In German' + optionalHeaders.locale = 'it' + String json = """ + { + "attributeName": "$attributeName", + } + """ + path(validAttributeNameId) + ok() + + when: + put(json) + + then: + payload.results.attributeNameLocale == attributeName + payload.results.attributeNameId == 1024 + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT create attributeNameLocale with attributeName id that exist but with invalid Locale'() { + given: + String attributeName = 'updated In German' + optionalHeaders.locale = invalidLocale + String json = """ + { + "attributeNameLocale": "$attributeName", + } + """ + path(validAttributeNameId) + invalid() + + when: + put(json) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'locale' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + + def 'can NOT create attributeNameLocale with attributeNameId that does not exist'() { + given: + String key = 'zzz_' + new Date() + String attributeName = "$key updated In German" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeNameLocale": "$attributeName", + } + """ + path(invalidAttributeNameId) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can NOT create attributeNameLocale with invalid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeName = "$key updated In German" + optionalHeaders.locale = invalidLocale + String json = """ + { + "attributeNameLocale": "$attributeName", + } + """ + path(validAttributeNameId) + invalid() + + when: + put(json) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'locale' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + def 'can NOT update attributeName with invalid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeName = "$key updated In German" + optionalHeaders.locale = invalidLocale + String json = """ + { + "attributeNameLocale": "$attributeName", + } + """ + path(validAttributeNameId) + invalid() + + when: + put(json) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'locale' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + def 'can NOT update attributeName with invalid AttributeName'() { + given: + String key = 'zzz_' + new Date() + String attributeName = "$key updated In German" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeNameLocale": "$attributeName", + } + """ + path(invalidAttributeNameId) + notFound() + + when: + put(json) + + then: + !payload + } + + @Ignore + def 'can update attributeName with valid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeName = "$key updated In German" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeNameLocale": "$attributeName", + } + """ + path(validAttributeNameId) + ok() + + when: + put(json) + + then: + payload.results.attributeNameLocale == attributeName + payload.results.attributeNameId == validAttributeNameId + payload.results.size() == ALL_COLUMNS_SIZE - 1 + } + + @Ignore + def 'can update attributeDisplayName with valid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeDisplayName = "$key updated In German in displayname" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeDisplayNameLocale": "$attributeDisplayName", + } + """ + path(validAttributeNameId) + ok() + + when: + put(json) + + then: + payload.results.attributeDisplayNameLocale == attributeDisplayName + payload.results.attributeNameId == validAttributeNameId + payload.results.size() == ALL_COLUMNS_SIZE - 1 + } + + + def 'can update attributeName and attributeDisplayName with NULL values for a valid Locale'() { + given: + String attributeName = '' + String attributeDisplayName = '' + optionalHeaders.locale = 'de' + String json = """ + { + "attributeName": "$attributeName", + "attributeDisplayName": "$attributeDisplayName", + } + """ + path(validAttributeNameId) + ok() + + when: + put(json) + + then: + payload.results.attributeDisplayNameLocale == null + payload.results.attributeNameId == validAttributeNameId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can update attributeName and attributeDisplayName with valid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeName = "$key updated Both In German" + String attributeDisplayName = "$key updated In German in displayname" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeName": "$attributeName", + "attributeDisplayName": "$attributeDisplayName", + } + """ + path(validAttributeNameId) + ok() + + when: + put(json) + + then: + payload.results.attributeDisplayNameLocale == attributeDisplayName + payload.results.attributeNameId == validAttributeNameId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT get all AttributeNames for invalid Locale'() { + given: + optionalHeaders.locale = invalidLocale + path() + ok() + + when: + get() + + then: + payload.meta.totalRecords == 0 + payload.results == [] + } + + def 'can get all AttributeNames for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path() + ok() + + when: + get() + + then: + apiPaginated() > 10 + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find AttributeNameLocale for an invalid AttributeName for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path(invalidAttributeNameId) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find AttributeNameLocale for a valid AttributeName for a invalid Locale'() { + given: + optionalHeaders.locale = invalidLocale + path(validAttributeNameId) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find AttributeNameLocale for invalid AttributeName for invalid Locale'() { + given: + optionalHeaders.locale = invalidLocale + path(invalidAttributeNameId) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find AttributeNameLocale for a valid AttributeName for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path(validAttributeNameId) + ok() + + when: + get() + + then: + payload.results.attributeNameId == validAttributeNameId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can get AttributeNameLocale for a valid AttributeName for a valid Locale'() { + given: + optionalHeaders.locale = 'fr' + path(validAttributeNameId) + ok() + + when: + get() + + then: + payload.results.attributeNameId == validAttributeNameId + payload.results.size() == ALL_COLUMNS_SIZE + } +} Index: src/integration-test/groovy/com/lemans/ds/attribute/AttributePartFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/attribute/AttributePartFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/attribute/AttributePartFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,85 @@ +package com.lemans.ds.attribute + +import com.lemans.ds.testing.DsFuncSpec + +class AttributePartFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { '' } + + final static int ALL_COLUMNS_SIZE = 10 + + def 'can get all parts filtered by attribute Name'() { + given: + path('attributeName/1081/part') + ok() + + when: + get() + + then: + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + def 'can get all parts filtered by attribute Name and category'() { + given: + path('attributeName/1081/part') + queryParams.categoryId = 592 + ok() + + when: + get() + + then: + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find part attributes with out attributeNameId'() { + given: + path('attributeName/null/part') + invalid() + + when: + get() + + then: + payload.messages[0].text == 'attributeNameId is required' + } + + def 'can get all parts filtered by attribute Value'() { + given: + path('attributeValue/101059/part') + ok() + + when: + get() + + then: + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + def 'can find all parts filtered by attribute Value and category'() { + given: + path('attributeValue/101059/part') + queryParams.categoryId = 6917 + ok() + + when: + get() + + then: + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find part attributes with out attributeValueId'() { + given: + path('attributeValue/null/part') + invalid() + + when: + get() + + then: + payload.messages[0].text == 'attributeValueId is required' + } +} Index: src/integration-test/groovy/com/lemans/ds/attribute/AttributeValueFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/attribute/AttributeValueFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/attribute/AttributeValueFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,245 @@ +package com.lemans.ds.attribute + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared + +class AttributeValueFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'attributeValue' } + + final static int ALL_COLUMNS_SIZE = 12 + + @Shared + Integer currentAttributeValueId + + def 'can not create attributeValue with blank attributeValue field'() { + given: + String json = '{ "attributeValue": "" }' + path() + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'attributeValue' + text.contains 'attributeValue is required' + } + payload.messages.size() == 1 + } + + def 'can not create duplicate attributeValue'() { + given: + String attributeValue = 'Small' + String json = """{ "attributeValue": "$attributeValue" }""" + path() + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + code == 'DUPLICATE_KEY' + } + payload.messages.size() == 1 + } + + def 'can create valid attributeValue '() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key TestCreate" + String json = """{ "attributeValue": "$attributeValue" }""" + path() + ok() + + when: + post(json) + currentAttributeValueId = payload.results.attributeValueId + then: + payload.results.attributeValue == attributeValue + payload.results.size() == ALL_COLUMNS_SIZE + } + + + //Test for Editing Attribute Value + def 'can NOT update attributeValue with invalid attributeValueId '() { + given: + Integer attributeValueId = -1 + String key = 'zzz_' + new Date() + String attributeValue = "$key updated" + String json = """{ "attributeValue": "$attributeValue" }""" + path(attributeValueId) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can NOT update attributeValue with invalid attributeValue'() { + given: + String longAttributeValue = '*' * (1000) + String attributeValue = longAttributeValue + String json = """{ "attributeValue": "$attributeValue" }""" + path(currentAttributeValueId) + invalid() + + when: + put(json) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'attributeValue' + text.contains 'exceeds the maximum size' + } + payload.messages.size() == 1 + } + + def 'can update attributeValue'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated" + String json = """{ "attributeValue": "$attributeValue" }""" + path(currentAttributeValueId) + ok() + + when: + put(json) + + then: + payload.results.attributeValue == attributeValue + payload.results.attributeValueId == currentAttributeValueId + payload.results.size() == ALL_COLUMNS_SIZE + } + + //////////////////////////////////////////////////////////////////////////// + + def 'can NOT find AttributeValue for invalid attributeValueId'() { + given: + path(-1) + notFound() + + when: + get() + + then: + !payload + } + + def 'can get all AttributeValues'() { + given: + path() + ok() + + when: + get() + + then: + apiPaginated() > 3 + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + def 'can get all AttributeValues with q params'() { + given: + String qParam = 'sm' + queryParams.q = qParam + path() + ok() + + when: + get() + + then: + apiPaginated() > 3 + payload.results[0].size() == ALL_COLUMNS_SIZE + payload.results.attributeValue.every { it.toLowerCase().contains(qParam) } + } + + def 'can find a AttributeValue'() { + given: + path(currentAttributeValueId) + ok() + + when: + get() + + then: + payload.results.attributeValueId == currentAttributeValueId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can not delete a AttributeValue if related to Category or Part'() { + given: + path(9) + invalid() + + when: + delete() + + then: + with(payload.messages[0]) { + type == 'error' + text.contains 'Cannot delete AttributeValue when assigned to Category or Part' + } + payload.messages.size() == 1 + } + + def 'can NOT delete AttributeValue with invalid AttributeNameId'() { + given: + path(-1) + notFound() + + when: + delete() + + then: + !payload + } + + def 'can delete a AttributeValue'() { + given: + path(currentAttributeValueId) + ok() + + when: + delete() + + then: + !payload + } + + def 'can get attributeValues by valid Attribute Name Id'() { + given: + Integer id = 1 + path(attributeName: id) + ok() + + when: + get() + + then: + payload.results.attributeValueId.contains(3) + payload.results.attributeValue.contains('Medium') + } + + def 'can NOT get attribute values with invalid attribute name Id'() { + given: + Integer id = 10 + path(attributeName: id) + ok() + + when: + get() + + then: + payload.results == [] + } +} Index: src/integration-test/groovy/com/lemans/ds/attribute/AttributeValueLocaleFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/attribute/AttributeValueLocaleFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/attribute/AttributeValueLocaleFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,281 @@ +package com.lemans.ds.attribute + +import com.lemans.ds.testing.DsFuncSpec + +class AttributeValueLocaleFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'attributeValue' } + + final static int ALL_COLUMNS_SIZE = 14 + + + Integer invalidAttributeValueId = 118332 + Integer validAttributeValueId = 39418 + + + def 'can create AttributeValuesLocale with attributeValueId that exist'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated In German" + optionalHeaders.locale = 'it' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path(validAttributeValueId) + ok() + + when: + put(json) + + then: + payload.results.attributeValueLocale == attributeValue + payload.results.attributeValueId == validAttributeValueId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT create attributeValueLocale with attributeValueId that exist but with invalid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated In German" + optionalHeaders.locale = 'xy' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path(validAttributeValueId) + invalid() + + when: + put(json) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'locale' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + + def 'can NOT create attributeValueLocale with attributeValueId that does not exist'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated In German" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path(invalidAttributeValueId) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can NOT create attributeValueLocale with invalid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated In German" + optionalHeaders.locale = 'xy' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path(validAttributeValueId) + invalid() + + when: + put(json) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'locale' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + def 'can NOT update attributeValue with invalid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated In German" + optionalHeaders.locale = 'xy' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path(validAttributeValueId) + invalid() + + when: + put(json) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'locale' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + def 'can NOT update attributeValue with invalid AttributeValueId'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated In German" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path(invalidAttributeValueId) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can update attributeValue with valid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated In German" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path(validAttributeValueId) + ok() + + when: + put(json) + + then: + payload.results.attributeValueLocale == attributeValue + payload.results.attributeValueId == validAttributeValueId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can update empty attributeValue with valid Locale'() { + given: + String attributeValue = '' + optionalHeaders.locale = 'de' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path(validAttributeValueId) + ok() + + when: + put(json) + + then: + payload.results.attributeValueLocale == null + payload.results.attributeValueId == validAttributeValueId + payload.results.size() == ALL_COLUMNS_SIZE + } + + + def 'can NOT get all AttributeValues for invalid Locale'() { + given: + optionalHeaders.locale = 'xy' + path() + ok() + + when: + get() + + then: + payload.results == [] + payload.meta.totalRecords == 0 + } + + def 'can get all AttributeValues for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path() + ok() + + when: + get() + + then: + apiPaginated() > 10 + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find AttributeValueLocale for an invalid AttributeValueId for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path(invalidAttributeValueId) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find AttributeValueLocale for a valid AttributeValue for a invalid Locale'() { + given: + optionalHeaders.locale = 'xy' + path(validAttributeValueId) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find AttributeValueLocale for invalid AttributeValue for invalid Locale'() { + given: + optionalHeaders.locale = 'xy' + path(invalidAttributeValueId) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find AttributeValueLocale for a valid AttributeValue for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path(validAttributeValueId) + ok() + + when: + get() + + then: + payload.results.attributeValueId == validAttributeValueId + payload.results.size() == ALL_COLUMNS_SIZE + } + + +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,324 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Stepwise + +@Stepwise +class CategoryAttributeFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'attribute' } + + final static int ALL_COLUMNS_SIZE = 22 + + final static int CATALOG_INSTANCE_ID = 0 + + final static int CATEGORY_ID = 1 + + final static int ATTRIBUTE_NAME_ID = 1 + + + def 'clean CategoryAttribute data'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], ATTRIBUTE_NAME_ID) + ignore() + + when: + delete() + + then: + !payload + } + + def 'can not create Category Attribute without required Fields'() { + given: + String json = """ +{ +"attributeNameId": $ATTRIBUTE_NAME_ID, +"categoryId": $CATEGORY_ID, +"isRequired": true, + }""" + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID]) + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'allowMultipleValues' + text.contains 'allowMultipleValues is required' + } + payload.messages.size() == 5 + } + + def 'can create valid Category AttributeName relation'() { + given: + String json = """ +{ +"attributeNameId": $ATTRIBUTE_NAME_ID, +"isRequired": true, +"isDropdown": true, +"isHidden": true, +"isGroup": true, +"isKeyAttribute": true, +"allowMultipleValues": false + }""" + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID]) + ok() + + when: + post(json) + + then: + with(payload) { + results.attributeNameId == ATTRIBUTE_NAME_ID + results.isRequired == true + results.size() == ALL_COLUMNS_SIZE + } + } + + def 'can not create duplicate attributeName'() { + given: + String json = """ +{ +"attributeNameId": $ATTRIBUTE_NAME_ID, +"categoryId": $CATALOG_INSTANCE_ID, +"isRequired": true, +"isDropdown": true, +"isHidden": true, +"isGroup": true, +"isKeyAttribute": true, +"allowMultipleValues": false + }""" + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID]) + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + code == 'DUPLICATE_KEY' + } + payload.messages.size() == 1 + } + + def 'can NOT update CategoryAttribute with invalid value'() { + given: + String json = ''' +{ +"isRequired": true, +"isDropdown": true, +"isHidden": true, +"isGroup": true, +"allowMultipleValues": null + }''' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], ATTRIBUTE_NAME_ID) + invalid() + + when: + put(json) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'allowMultipleValues' + text.contains 'allowMultipleValues is required' + } + payload.messages.size() == 1 + } + + def 'can NOT update CategoryAttribute with invalid attributeId'() { + given: + String json = ''' +{ +"isRequired": true, +"isDropdown": true, +"isHidden": true, +"isGroup": true, +"allowMultipleValues": null + }''' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], -1) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can update CategoryAttribute with valid value'() { + given: + String json = '{ "allowMultipleValues": true}' + path([catalog: 0, category: CATEGORY_ID], ATTRIBUTE_NAME_ID) + ok() + + when: + put(json) + + then: + with(payload) { + results.attributeNameId == ATTRIBUTE_NAME_ID + results.allowMultipleValues == true + results.size() == ALL_COLUMNS_SIZE + } + } + + def 'can update IsDropDown with valid value'() { + given: + String json = '{ "isDropdown": true}' + path([catalog: 0, category: CATEGORY_ID], ATTRIBUTE_NAME_ID) + ok() + + when: + put(json) + + then: + with(payload) { + results.attributeNameId == ATTRIBUTE_NAME_ID + results.isDropdown == true + results.size() == ALL_COLUMNS_SIZE + } + } + + def 'can NOT update CategoryAttribute allowMultipleValues to false if parts has multiple values assigned '() { + given: + String json = '{ "allowMultipleValues": false}' + path([catalog: 0, category: 626], 1572) + invalid() + + when: + put(json) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'allowMultipleValues' + text.contains 'Cannot alter' + } + payload.messages.size() == 1 + } + + def 'can NOT find a CategoryAttribute for a Catalog that does not exist'() { + given: + path([catalog: -1, category: 1], 1) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find a CategoryAttribute for a Category that does not exist'() { + given: + path([catalog: 0, category: -1], 1) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find a CategoryAttribute'() { + given: + int catalogInstanceId = 0 + int categoryId = 1 + int attributeNameId = 1 + path([catalog: catalogInstanceId, category: categoryId], attributeNameId) + ok() + + when: + get() + + then: + payload.results.categoryId == categoryId + payload.results.catalogInstanceId == catalogInstanceId + payload.results.attributeNameId == attributeNameId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find CategoryAttributes for a Catalog that does not exist'() { + given: + path([catalog: -1, category: 1]) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find CategoryAttributes for a Category that does not exist'() { + given: + path([catalog: 0, category: -1]) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find CategoriesAttributes for a category with all columns'() { + given: + int catalogInstanceId = 0 + int categoryId = 1 + path([catalog: catalogInstanceId, category: categoryId]) + ok() + + when: + get() + + then: + payload.results.catalogInstanceId.every { it == catalogInstanceId } + payload.results.size() >= 1 + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + + def 'can not delete a CategoryAttribute with invalid categoryId'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: -1], ATTRIBUTE_NAME_ID) + notFound() + + when: + delete() + + then: + !payload + } + + def 'can NOT delete a CategoryAttribute that has parts with the same attribute name'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: 4542], 510) + invalid() + + when: + delete() + + then: + payload.messages[0].text == 'Cannot delete Category Attribute that has parts with the same attribute Name.' + } + + def 'can delete a CategoryAttribute'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], ATTRIBUTE_NAME_ID) + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeLocaleFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeLocaleFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeLocaleFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,318 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec + +class CategoryAttributeLocaleFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'attribute' } + + final static int ALL_COLUMNS_SIZE = 25 + final static int CATALOG_INSTANCE_ID = 0 + final static int CATEGORY_ID = 4537 + final static int VALID_ATTRIBUTE_NAME_ID = 1081 + final static int INVALID_ATTRIBUTE_NAME_ID = 31 + + + + String invalidLocale = 'xy' + + def 'can create categoryAttributeLocale with categoryAttributeId that exist with valid Locale'() { + given: + String attributeName = 'updated In German' + optionalHeaders.locale = 'it' + String json = """ + { + "attributeName": "$attributeName", + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], VALID_ATTRIBUTE_NAME_ID) + ok() + + when: + put(json) + + then: + payload.results.attributeNameLocale == attributeName + payload.results.attributeNameId == VALID_ATTRIBUTE_NAME_ID + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT create categoryAttributeLocale with categoryAttributeId that exist but with invalid Locale'() { + given: + String attributeName = 'updated In German' + optionalHeaders.locale = invalidLocale + String json = """ + { + "attributeName": "$attributeName", + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], VALID_ATTRIBUTE_NAME_ID) + invalid() + + when: + put(json) + + then: + with(payload.messages[0]) { + type == 'error' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + + def 'can NOT create categoryAttributeLocale with categoryAttributeId that does not exist'() { + given: + String key = 'zzz_' + new Date() + String attributeName = "$key updated In German" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeName": "$attributeName", + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], INVALID_ATTRIBUTE_NAME_ID) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can NOT update categoryAttributeLocale with invalid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeName = "$key updated In German" + optionalHeaders.locale = invalidLocale + String json = """ + { + "attributeName": "$attributeName", + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], VALID_ATTRIBUTE_NAME_ID) + invalid() + + when: + put(json) + + then: + with(payload.messages[0]) { + type == 'error' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + def 'can NOT update categoryAttributeLocale with invalid categoryAttributeId'() { + given: + String key = 'zzz_' + new Date() + String attributeName = "$key updated In German" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeName": "$attributeName", + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], INVALID_ATTRIBUTE_NAME_ID) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can update categoryAttributeLocale with valid Locale'() { + given: + optionalHeaders.locale = 'de' + String json =''' + { + "isKeyAttribute": true, + "isHidden": true, + "allowMultipleValues": true, + "isGroup": true, + "isRequired": false, + } + ''' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], VALID_ATTRIBUTE_NAME_ID) + ok() + + when: + put(json) + + then: + payload.results.attributeNameId == VALID_ATTRIBUTE_NAME_ID + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can update categoryAttributeDisplayNameLocale with valid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeDisplayName = "$key updated In German in displayname" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeDisplayName": "$attributeDisplayName" + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], VALID_ATTRIBUTE_NAME_ID) + ok() + + when: + put(json) + + then: + payload.results.attributeDisplayNameLocale == attributeDisplayName + payload.results.attributeNameId == VALID_ATTRIBUTE_NAME_ID + payload.results.size() == ALL_COLUMNS_SIZE + } + + + def 'can update categoryAttributeLocale and categoryAttributeDisplayNameLocale with NULL values for a valid Locale'() { + given: + String attributeName = '' + String attributeDisplayName = '' + optionalHeaders.locale = 'de' + String json = """ + { + "attributeName": "$attributeName", + "attributeDisplayName": "$attributeDisplayName", + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], VALID_ATTRIBUTE_NAME_ID) + ok() + + when: + put(json) + + + then: + with(payload) { + //results.attributeDisplayNameLocale == null + results.attributeNameId == VALID_ATTRIBUTE_NAME_ID + results.size() == ALL_COLUMNS_SIZE + } + } + + def 'can update categoryAttributeLocale and categoryAttributeDisplayNameLocale with valid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeName = "$key updated Both In German" + String attributeDisplayName = "$key updated In German in displayname" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeName": "$attributeName", + "attributeDisplayName": "$attributeDisplayName", + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], VALID_ATTRIBUTE_NAME_ID) + ok() + + when: + put(json) + + then: + payload.results.attributeDisplayNameLocale == attributeDisplayName + payload.results.attributeNameId == VALID_ATTRIBUTE_NAME_ID + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT get all categoryAttributeLocale for invalid Locale'() { + given: + optionalHeaders.locale = invalidLocale + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID]) + ok() + + when: + get() + + then: + payload.results == [] + payload.meta.totalRecords == 0 + } + + def 'can get all categoryAttributeLocale for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID]) + ok() + + when: + get() + + then: + apiPaginated() > 10 + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find categoryAttributeLocale for an invalid categoryAttributeId for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], INVALID_ATTRIBUTE_NAME_ID) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find categoryAttributeLocale for a valid categoryAttributeId for a invalid Locale'() { + given: + + optionalHeaders.locale = invalidLocale + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], VALID_ATTRIBUTE_NAME_ID) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find categoryAttributeLocale for invalid categoryAttributeId for invalid Locale'() { + given: + optionalHeaders.locale = invalidLocale + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], INVALID_ATTRIBUTE_NAME_ID) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find categoryAttributeLocale for a invalid categoryAttributeId for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], INVALID_ATTRIBUTE_NAME_ID) + notFound() + + when: + get() + + then: + !payload + } + def 'can find categoryAttributeLocale for a valid categoryAttributeId for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], VALID_ATTRIBUTE_NAME_ID) + ok() + + when: + get() + + then: + payload.results.attributeNameId == VALID_ATTRIBUTE_NAME_ID + payload.results.size() == ALL_COLUMNS_SIZE + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeMoveFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeMoveFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeMoveFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,95 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared +import spock.lang.Stepwise + +@Stepwise +class CategoryAttributeMoveFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'attribute' } + + final static int CATALOG_INSTANCE_ID = 0 + + final static int CATEGORY_ID = 1 + + final static int SOURCE_ATTRIBUTE_NAME_ID = 1089 + + final static int TARGET_ATTRIBUTE_NAME_ID = 103 + + @Shared + Integer targetSequence + + def 'can find target CategoryAttribute'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], TARGET_ATTRIBUTE_NAME_ID) + ok() + + when: + get() + targetSequence = payload.results.sequence + then: + payload.results.categoryId == CATEGORY_ID + payload.results.catalogInstanceId == CATALOG_INSTANCE_ID + payload.results.attributeNameId == TARGET_ATTRIBUTE_NAME_ID + } + + def 'can re sequence or Move Category Attribute Before'() { + given: + Integer assertSequence = targetSequence + String json = """ +{ +"targetId": $TARGET_ATTRIBUTE_NAME_ID, +"position": "BEFORE" +} +""" + path([catalog: 0, category: CATEGORY_ID], SOURCE_ATTRIBUTE_NAME_ID + '/move') + ok() + + when: + put(json) + + then: + with(payload) { + results.attributeNameId == SOURCE_ATTRIBUTE_NAME_ID + results.sequence <= assertSequence + } + } + + def 'can find target CategoryAttribute again'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID], TARGET_ATTRIBUTE_NAME_ID) + ok() + + when: + get() + targetSequence = payload.results.sequence + then: + payload.results.categoryId == CATEGORY_ID + payload.results.catalogInstanceId == CATALOG_INSTANCE_ID + payload.results.attributeNameId == TARGET_ATTRIBUTE_NAME_ID + } + + def 'can re sequence or Move Category Attribute After'() { + given: + Integer assertSequence = targetSequence + String json = """ +{ +"targetId": $TARGET_ATTRIBUTE_NAME_ID, +"position": "AFTER" +} +""" + path([catalog: 0, category: CATEGORY_ID], SOURCE_ATTRIBUTE_NAME_ID + '/move') + ok() + + when: + put(json) + + then: + with(payload) { + results.attributeNameId == SOURCE_ATTRIBUTE_NAME_ID + results.sequence >= assertSequence + } + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeValueFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeValueFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeValueFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,171 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Stepwise + +@Stepwise +class CategoryAttributeValueFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'value' } + + final static int ALL_COLUMNS_SIZE = 20 + + final static int CATALOG_INSTANCE_ID = 0 + + final static int CATEGORY_ID = 4537 + + final static int ATTRIBUTE_NAME_ID = 1081 + + final static int ATTRIBUTE_VALUE_ID = 9 + + def 'clean test data'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], ATTRIBUTE_VALUE_ID) + ignore() + when: + delete() + + then: + !payload + } + + def 'can not create CategoryAttributeValue with invalid attributeNameId'() { + given: + String json = """ +{ +"attributeValueId": $ATTRIBUTE_VALUE_ID, + }""" + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: -1]) + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + text.contains 'attributeName relation not found' + } + payload.messages.size() == 1 + } + + def 'can create valid CategoryAttributeValue relation'() { + given: + String json = """ +{ +"attributeValueId": $ATTRIBUTE_VALUE_ID, + }""" + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID]) + ok() + + when: + post(json) + + then: + with(payload) { + results.attributeValueId == ATTRIBUTE_VALUE_ID + results.size() == ALL_COLUMNS_SIZE + } + + } + + def 'can not create duplicate CategoryAttributeValue relation'() { + given: + String json = """ +{ +"attributeValueId": $ATTRIBUTE_VALUE_ID, + }""" + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID]) + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + code == 'DUPLICATE_KEY' + } + payload.messages.size() == 1 + } + + def 'can NOT find a CategoryAttributeValue for a Catalog that does not exist'() { + given: + path([catalog: -1, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], ATTRIBUTE_VALUE_ID) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find a CategoryAttributeValue'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], ATTRIBUTE_VALUE_ID) + ok() + + when: + get() + + then: + payload.results.categoryId == CATEGORY_ID + payload.results.catalogInstanceId == CATALOG_INSTANCE_ID + payload.results.attributeNameId == ATTRIBUTE_NAME_ID + payload.results.attributeValueId == ATTRIBUTE_VALUE_ID + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find CategoryAttributeValues for a Category that does not exist'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: -1, attribute: ATTRIBUTE_NAME_ID], ATTRIBUTE_VALUE_ID) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find CategoryAttributeValues for a valid request with all columns'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID]) + ok() + + when: + get() + + then: + payload.results.catalogInstanceId.every { it == CATALOG_INSTANCE_ID } + payload.results.size() >= 1 + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + + def 'can not delete a CategoryAttributeValue with invalid categoryId'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: -1, attribute: ATTRIBUTE_NAME_ID], ATTRIBUTE_VALUE_ID) + notFound() + + when: + delete() + + then: + !payload + } + + def 'can delete a CategoryAttributeValue'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], ATTRIBUTE_VALUE_ID) + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeValueLocaleFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeValueLocaleFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeValueLocaleFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,238 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec + +class CategoryAttributeValueLocaleFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'value' } + + final static int ALL_COLUMNS_SIZE = 23 + + final static int CATALOG_INSTANCE_ID = 0 + + final static int CATEGORY_ID = 4537 + + final static int ATTRIBUTE_NAME_ID = 1081 + + final static int VALID_CATEGORY_ATTRIBUTE_VALUE_ID = 63296 + + final static int INVALID_CATEGORY_ATTRIBUTE_VALUE_ID = 0 + + + def 'can create categoryAttributeValueLocale with categoryAttributeValueId that exist with valid locale'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated In German" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], VALID_CATEGORY_ATTRIBUTE_VALUE_ID) + ok() + + when: + put(json) + + then: + payload.results.attributeValueId == VALID_CATEGORY_ATTRIBUTE_VALUE_ID + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT create categoryAttributeValueLocale with categoryAttributeValueId that exist but with invalid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated In German" + optionalHeaders.locale = 'xy' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], VALID_CATEGORY_ATTRIBUTE_VALUE_ID) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can NOT create attributeValueLocale with attributeValueId that does not exist'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated In German" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], INVALID_CATEGORY_ATTRIBUTE_VALUE_ID) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can NOT update attributeValue with invalid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated In German" + optionalHeaders.locale = 'xy' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], VALID_CATEGORY_ATTRIBUTE_VALUE_ID) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can NOT update attributeValue with invalid AttributeValueId'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated In German" + optionalHeaders.locale = 'de' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], INVALID_CATEGORY_ATTRIBUTE_VALUE_ID) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can update attributeValue with valid Locale'() { + given: + String key = 'zzz_' + new Date() + String attributeValue = "$key updated In German" + optionalHeaders.locale = 'es' + String json = """ + { + "attributeValue": "$attributeValue", + } + """ + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], VALID_CATEGORY_ATTRIBUTE_VALUE_ID) + ok() + + when: + put(json) + + then: + //payload.results.attributeValueLocale == attributeValue + payload.results.attributeValueId == VALID_CATEGORY_ATTRIBUTE_VALUE_ID + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT get all AttributeValues for invalid Locale'() { + given: + optionalHeaders.locale = 'xy' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID]) + notFound() + + when: + get() + + then: + !payload + } + + def 'can get all AttributeValues for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID]) + ok() + + when: + get() + + then: + //apiPaginated() > 1 + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find AttributeValueLocale for an invalid AttributeValueId for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], INVALID_CATEGORY_ATTRIBUTE_VALUE_ID) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find AttributeValueLocale for a valid AttributeValue for a invalid Locale'() { + given: + optionalHeaders.locale = 'xy' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], VALID_CATEGORY_ATTRIBUTE_VALUE_ID) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find AttributeValueLocale for invalid AttributeValue for invalid Locale'() { + given: + optionalHeaders.locale = 'xy' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], INVALID_CATEGORY_ATTRIBUTE_VALUE_ID) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find AttributeValueLocale for a valid AttributeValue for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], VALID_CATEGORY_ATTRIBUTE_VALUE_ID) + ok() + + when: + get() + + then: + payload.results.attributeValueId == VALID_CATEGORY_ATTRIBUTE_VALUE_ID + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find AttributeValueLocale for a invalid AttributeValue for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], INVALID_CATEGORY_ATTRIBUTE_VALUE_ID) + notFound() + + when: + get() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeValueMoveFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeValueMoveFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryAttributeValueMoveFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,99 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared +import spock.lang.Stepwise + +@Stepwise +class CategoryAttributeValueMoveFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'value' } + + final static int CATALOG_INSTANCE_ID = 0 + + final static int CATEGORY_ID = 1 + + final static int ATTRIBUTE_NAME_ID = 103 + + final static int SOURCE_ATTRIBUTE_VALUE_ID = 11 + + final static int TARGET_ATTRIBUTE_VALUE_ID = 67036 + + @Shared + Integer targetSequence + + def 'can find target CategoryAttributeValue'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], TARGET_ATTRIBUTE_VALUE_ID) + ok() + + when: + get() + targetSequence = payload.results.sequence + then: + payload.results.categoryId == CATEGORY_ID + payload.results.catalogInstanceId == CATALOG_INSTANCE_ID + payload.results.attributeValueId == TARGET_ATTRIBUTE_VALUE_ID + } + + def 'can re sequence or Move Category Attribute Value Before'() { + given: + Integer assertSequence = targetSequence + String json = """ +{ +"targetId": $TARGET_ATTRIBUTE_VALUE_ID, +"position": "BEFORE" +} +""" + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], SOURCE_ATTRIBUTE_VALUE_ID + '/move') + ok() + + when: + put(json) + + then: + with(payload) { + results.attributeValueId == SOURCE_ATTRIBUTE_VALUE_ID + results.sequence + results.sequence <= assertSequence + } + } + + def 'can find target CategoryAttributeValue again'() { + given: + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], TARGET_ATTRIBUTE_VALUE_ID) + ok() + + when: + get() + targetSequence = payload.results.sequence + then: + payload.results.categoryId == CATEGORY_ID + payload.results.catalogInstanceId == CATALOG_INSTANCE_ID + payload.results.attributeValueId == TARGET_ATTRIBUTE_VALUE_ID + } + + def 'can re sequence or Move Category AttributeValue After'() { + given: + Integer assertSequence = targetSequence + String json = """ +{ +"targetId": $TARGET_ATTRIBUTE_VALUE_ID, +"position": "AFTER" +} +""" + path([catalog: CATALOG_INSTANCE_ID, category: CATEGORY_ID, attribute: ATTRIBUTE_NAME_ID], SOURCE_ATTRIBUTE_VALUE_ID + '/move') + ok() + + when: + put(json) + + then: + with(payload) { + results.attributeValueId == SOURCE_ATTRIBUTE_VALUE_ID + results.sequence + results.sequence >= assertSequence + } + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryDetailsFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryDetailsFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryDetailsFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,69 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec + +class CategoryDetailsFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'details' } + + final static int ALL_COLUMNS_SIZE = 21 + + def 'can NOT find a Category for a Catalog that does not exist'() { + given: + path([catalog: 0, category: -1]) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find a Category details for a valid Catalog for a valid Locale'() { + given: + int categoryId = 626 + optionalHeaders.locale = 'de' + path([catalog: 0, category: categoryId]) + ok() + + when: + get() + + then: + payload.results.categoryId == categoryId + payload.results.attribute.size() > 1 + } + + def 'can find a Category details for a valid Catalog'() { + given: + int categoryId = 626 + path([catalog: 0, category: categoryId]) + ok() + + when: + get() + + then: + payload.results.categoryId == categoryId + payload.results.attribute.size() > 1 + } + + def 'can find a Category details for a Locale for valid Catalog'() { + given: + int categoryId = 626 + optionalHeaders.locale = 'de' + path([catalog: 0, category: categoryId]) + ok() + + when: + get() + + then: + payload.results.categoryId == categoryId + payload.results.attribute.size() > 1 + } + + +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,115 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.IgnoreRest + +class CategoryFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'category' } + + final static int ALL_COLUMNS_SIZE = 21 + + def 'can NOT find a Category for a Catalog that does not exist'() { + given: + path([catalog: 0], -1) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find a Category for a Catalog'() { + given: + int catalogInstanceId = 0 + int categoryId = 1 + path([catalog: catalogInstanceId], categoryId) + ok() + + when: + get() + + then: + payload.results.categoryId == categoryId + payload.results.catalogInstanceId == catalogInstanceId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find Categories for a Catalog that does not exist'() { + given: + path([catalog: -1]) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find Categories for a Catalog with ALL columns'() { + given: + int catalogId = 0 + path([catalog: catalogId]) + ok() + + when: + get() + + then: + payload.results.catalogInstanceId.every { it == catalogId } + payload.results.size() >= 7 + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + @IgnoreRest + def 'can find specific Categories for a Catalog with ALL columns'() { + given: + int catalogId = 0 + path([catalog: catalogId]) + queryParams.categoryId = '1,2' + ok() + + when: + get() + + then: + payload.results.catalogInstanceId.every { it == catalogId } + payload.results.categoryId == [1, 2] + payload.results.size() == 2 + payload.results[0].size() == ALL_COLUMNS_SIZE + } + + def 'can find Categories for a Catalog with ONLY tree columns'() { + given: + int catalogId = 0 + queryParams.columns = '_tree' + path([catalog: catalogId]) + ok() + + when: + get() + + then: + payload.results.size() >= 7 + payload.results[0].size() == CategoryService.TREE_COLUMNS.size() + } + + def 'can find Categories for a Catalog with ONLY dropdown columns'() { + given: + int catalogId = 0 + queryParams.columns = '_dropdown' + path([catalog: catalogId]) + ok() + + when: + get() + + then: + payload.results.size() >= 7 + payload.results[0].size() == CategoryService.DROPDOWN_COLUMNS.size() + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryHierarchyPersistenceSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryHierarchyPersistenceSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryHierarchyPersistenceSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,96 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared +import spock.lang.Stepwise + +@Stepwise +class CategoryHierarchyPersistenceSpec extends DsFuncSpec { + + @Override + String resourceName() { 'category' } + + @Shared Integer childCategoryId + + @Shared Integer parentCategoryId = 2 + + @Shared Integer catalogId = 0 + + def 'can add a child Category to a parent'() { + given: + String name = 'testName_' + new Date() + Map category = [catalogInstanceId: catalogId, categoryName: name, parentCategoryId: parentCategoryId] + path(catalog: catalogId) + ok() + + when: + post(category) + childCategoryId = payload.results.categoryId + + then: + with(payload.results) { + catalogInstanceId == this.catalogId + categoryName == name + parentCategoryId == this.parentCategoryId + categoryId != null + sequence > 1 + } + } + + def 'can NOT update a child Category name making it a duplicate'() { + given: + int idOfDuplicated = 6 + String nameOfDuplicated = 'Child Level 3' + Map category = [categoryName: nameOfDuplicated, version: -9999] + path([catalog: catalogId], idOfDuplicated) + invalid() + + when: + put(category) + + then: + payload.messages.find { + it.code == 'DATA_INTEGRITY_VIOLATION' || it.code == 'DUPLICATE_KEY' + } + } + + def 'can delete the new child Category without children'() { + given: + queryParams.version = -9999 + path([catalog: catalogId], childCategoryId) + ok() + + when: + delete() + + then: + !payload + } + + def 'can NOT add a child Category to a deleted parent'() { + given: + String name = 'testName_' + new Date() + Map category = [catalogInstanceId: catalogId, categoryName: name, parentCategoryId: 301] + path(catalog: catalogId) + notFound() + + when: + post(category) + + then: + !payload + } + + def 'can NOT add a child Category to a parent exceeding the depth limitation'() { + given: + Map category = [catalogInstanceId: catalogId, categoryName: 'testName_' + new Date(), parentCategoryId: 9] + path(catalog: catalogId) + invalid() + + when: + post(category) + + then: + payload.messages.find { it.text == "Category depth of $Category.MAX_DEPTH may not be exceeded" } + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryIntegrationSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryIntegrationSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryIntegrationSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,27 @@ +package com.lemans.ds.category + +import ds.service.Application +import grails.test.mixin.integration.Integration +import spock.lang.Shared +import spock.lang.Specification + +@Integration(applicationClass = Application) +class CategoryIntegrationSpec extends Specification { + + @Shared def categoryManagerService + + def 'can find Categories for a catalogInstanceId'() { + given: + int catalogInstanceId = 0 + + when: + List categories = categoryManagerService.findCategoriesForCatalog(catalogInstanceId) + categories.each { + } + + then: + categories.catalogInstanceId.every { it == catalogInstanceId } + categories.dateDeleted.every { it == null } + categories.size() > 4 + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryLocaleFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryLocaleFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryLocaleFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,41 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec + +class CategoryLocaleFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'category' } + + Integer categoryId = 1 + String localePresent = 'es' + + def 'can update or create categoryLocale'() { + given: + Map data = [categoryName: 'Updating testCategoryLocale', description: 'Updating Locale creating'] + queryParams.locale = localePresent + path([catalog: 0], categoryId) + ok() + + when: + put(data) + + then: + payload.results.categoryNameLocale == 'Updating testCategoryLocale' + payload.results.descriptionLocale == 'Updating Locale creating' + } + + def 'can get locale values for a categoryId'() { + given: + queryParams.locale = localePresent + path([catalog: 0], categoryId) + ok() + + when: + get() + + then: + payload.results.categoryId == categoryId + payload.results.locale == localePresent + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryMoveFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryMoveFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryMoveFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,159 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Stepwise + +@Stepwise +class CategoryMoveFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'move' } + + def 'can NOT move a Category with invalid targetId or position'() { + given: + path(catalog: 0, category: 305) + invalid() + + when: + put() + + then: + with(payload) { + messages.find { it.text == 'targetId is required' } + messages.find { it.text == 'position is required' } + } + } + + def 'can NOT move a Category INSIDE another Category if it would create a cycle'() { + given: + int targetId = 3 + int categoryId = 2 + Map body = [targetId: targetId, position: 'INSIDE'] + path(catalog: 0, category: categoryId) + invalid() + + when: + put(body) + + then: + payload.messages.find { it.text == 'Category may not be moved to create a cycle' } + } + + def 'can NOT move a Category AFTER another Category if it would create a cycle'() { + given: + int targetId = 3 + int categoryId = 2 + Map body = [targetId: targetId, position: 'AFTER'] + path(catalog: 0, category: categoryId) + invalid() + + when: + put(body) + + then: + payload.messages.find { it.text == 'Category may not be moved to create a cycle' } + } + + def 'can NOT move a Category INSIDE another Category exceeding the depth limitation'() { + given: + Map body = [targetId: 9, position: 'INSIDE'] + path(catalog: 0, category: 272) + invalid() + + when: + put(body) + + then: + payload.messages.find { it.text == "Category depth of $Category.MAX_DEPTH may not be exceeded" } + } + + def 'can NOT move a Category AFTER another Category exceeding the depth limitation'() { + given: + Map body = [targetId: 9, position: 'INSIDE'] + path(catalog: 0, category: 272) + invalid() + + when: + put(body) + + then: + payload.messages.find { it.text == "Category depth of $Category.MAX_DEPTH may not be exceeded" } + } + + def 'can NOT move a Category BEFORE another Category exceeding the depth limitation'() { + given: + Map body = [targetId: 9, position: 'INSIDE'] + path(catalog: 0, category: 272) + invalid() + + when: + put(body) + + then: + payload.messages.find { it.text == "Category depth of $Category.MAX_DEPTH may not be exceeded" } + } + + def 'can move a Category AFTER a SIBLING Category'() { + given: + int categoryId = 305 + int targetId = 300 + Map body = [targetId: targetId, position: 'AFTER'] + path(catalog: 0, category: categoryId) + ok() + + when: + put(body) + + then: + Thread.sleep(1000) + payload.results.sequence == 6 + } + + def 'can move a Category BEFORE a SIBLING Category'() { + given: + int categoryId = 305 + int targetId = 300 + Map body = [targetId: targetId, position: 'BEFORE'] + path(catalog: 0, category: categoryId) + ok() + + when: + put(body) + + then: + Thread.sleep(1000) + payload.results.sequence == 5 + } + + def 'can move a Category to the first sibling UNDER another Category'() { + given: + int targetId = 80 + int categoryId = 81 + Map body = [targetId: targetId, position: 'INSIDE'] + path(catalog: 1, category: categoryId) + ok() + + when: + put(body) + + then: + payload.results.parentCategoryId == targetId + payload.results.sequence == 1 + } + + def 'can move a Category back alongside AFTER its original sibling Category'() { + given: + int targetId = 80 + int categoryId = 81 + Map body = [targetId: targetId, position: 'AFTER'] + path(catalog: 1, category: categoryId) + ok() + + when: + put(body) + + then: + payload.results.parentCategoryId == null + payload.results.sequence == 3 + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryPartFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryPartFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryPartFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,95 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Stepwise + +@Stepwise +class CategoryPartFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'part' } + + int categoryId = 434 + int catalogId = 0 + Map mapping = [catalog: catalogId, category: categoryId] + + def 'can NOT add Parts to an invalid Category'() { + given: + List partNumbers = ['B8ES', 'B9ES'] + String action = 'add' + path([catalog: -1, category: 2]) + invalid() + + when: + put([partNumbers: partNumbers, action: action]) + + then: + with(payload.messages[0]) { + text == 'Invalid category' + type == 'error' + } + } + + def 'can NOT add invalid Parts to a Category'() { + given: + List partNumbers = ['B10ES', '99912345'] + String action = 'add' + path(mapping) + invalid() + + when: + put([partNumbers: partNumbers, action: action]) + + then: + with(payload.messages[0]) { + text == 'Invalid parts found [99912345]' + type == 'error' + } + } + + def 'can not perform add Parts to a Category without any parts'() { + given: + String action = 'add' + path(mapping) + invalid() + + when: + put([action: action]) + + then: + with(payload.messages[0]) { + text == 'No part selected' + type == 'error' + } + } + + def 'can add Parts to a Category'() { + given: + List partNumbers = ['B8ES', 'B9ES'] + String action = 'add' + path(mapping) + ok() + + when: + put([partNumbers: partNumbers, action: action]) + + then: + !payload.messages + payload.results == [] + } + + def 'can remove Parts from a Category'() { + given: + List partNumbers = ['B8ES', 'B9ES'] + String action = 'remove' + path(mapping) + ok() + + when: + put([partNumbers: partNumbers, action: action]) + + then: + !payload.messages + payload.results == [] + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryPersistenceFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryPersistenceFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryPersistenceFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,156 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared +import spock.lang.Stepwise + +@Stepwise +class CategoryPersistenceFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'category' } + + @Shared Integer theCategoryId + + @Shared Integer catalogId = 0 + + def 'can NOT delete a root Category that does not exist'() { + given: + path([catalog: catalogId], -1) + notFound() + + when: + delete() + + then: + !payload + } + + def 'can NOT create an invalid root Category for a Catalog'() { + given: + Map category = [catalogInstanceId: catalogId] + path(catalog: catalogId) + invalid() + + when: + post(category) + + then: + payload.messages[0].field == 'categoryName' + payload.messages[0].text == 'Category Name is required' + } + + def 'can create a root Category for a Catalog'() { + given: + String name = 'testName_' + new Date() + Map category = [catalogInstanceId: catalogId, categoryName: name] + path(catalog: catalogId) + ok() + + when: + post(category) + theCategoryId = payload.results.categoryId + + then: + with(payload.results) { + catalogInstanceId == this.catalogId + categoryName == name + parentCategoryId == null + sequence + categoryId + } + } + + def 'can NOT create a duplicate root Category for a Catalog'() { + given: + String name = 'Parent Category 1' + Map category = [catalogInstanceId: catalogId, categoryName: name] + path(catalog: catalogId) + invalid() + + when: + post(category) + + then: + payload.messages.find { it.code == 'DUPLICATE_KEY' } + } + + def 'can update a Category'() { + given: + String description = 'test_' + new Date() + Map category = [description: description, version: -9999] + path([catalog: catalogId], theCategoryId) + ok() + + when: + put(category) + + then: + payload.results.description == description + } + + def 'can NOT update a Category catalogInstanceId'() { + given: + Map category = [catalogInstanceId: 1] + path([catalog: catalogId], theCategoryId) + invalid() + + when: + put(category) + + then: + payload.messages.find { it.field == 'catalogInstanceId' }.text == 'Catalog may not be changed' + } + + def 'can NOT update a Category parentCategoryId directly'() { + given: + Map category = [parentCategoryId: 1] + path([catalog: catalogId], theCategoryId) + invalid() + + when: + put(category) + + then: + payload.messages.find { it.field == 'parentCategoryId' }.text == 'Category Parent may not be changed' + } + + def 'can NOT update a Category sequence directly'() { + given: + Map category = [sequence: 1] + path([catalog: catalogId], theCategoryId) + invalid() + + when: + put(category) + + then: + payload.messages.find { it.field == 'sequence' }.text == 'Category Sequence may not be changed' + } + + def 'can delete a root Category without children'() { + given: + queryParams.version = -9999 + path([catalog: catalogId], theCategoryId) + ok() + + when: + delete() + + then: + !payload + } + + def 'can NOT delete a root Category with children'() { + given: + queryParams.version = -9999 + path([catalog: catalogId], 2) + invalid() + + when: + delete() + + then: + payload.messages.find { it.text == 'Category may not be deleted' } + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategoryProductFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategoryProductFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategoryProductFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,112 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec + + +class CategoryProductFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'product' } + + int categoryId = 434 + int catalogId = 0 + int product1 = 343317 + //int product2 = 343317 + Map mapping = [catalog: catalogId, category: categoryId] + + def 'can NOT add Product to an invalid Catalog'() { + given: + List productIds = [product1, 1234] + String action = 'INSERT' + path([catalog: -1, category: 2]) + invalid() + + when: + post([productIds: productIds, operation: action]) + + then: + with(payload.messages[0]) { + text == 'Invalid category or catalog' + type == 'error' + } + } + + def 'can NOT add Product to an invalid Category'() { + given: + List productIds = [product1, 1234] + String action = 'INSERT' + path([catalog: catalogId, category: -1]) + invalid() + + when: + post([productIds: productIds, operation: action]) + + then: + with(payload.messages[0]) { + text == 'Invalid category or catalog' + type == 'error' + } + } + + def 'can NOT add category to Invalid productId'() { + given: + List productIds = [product1, 1234] + String action = 'INSERT' + path([catalog: catalogId, category: categoryId]) + invalid() + + when: + post([productIds: productIds, operation: action]) + + then: + with(payload.messages[0]) { + text.contains('Invalid productId') + type == 'error' + } + } + + /*def 'can NOT add category to products with parts of different category '() { + given: + List productIds = [product1] + String action = 'INSERT' + path([catalog: catalogId, category: categoryId]) + invalid() + + when: + post([productIds: productIds, operation: action]) + + then: + with(payload.messages[0]) { + text.contains('Invalid productId') + type == 'error' + } + }*/ + + def 'can add category to products'() { + given: + List productIds = [product1] + String action = 'INSERT' + path([catalog: catalogId, category: categoryId]) + ok() + + when: + post([productIds: productIds, operation: action]) + + then: + !payload.results + } + + def 'can remove Category for product'() { + given: + List productIds = [product1] + String action = 'DELETE' + path([catalog: catalogId, category: categoryId]) + ok() + + when: + post([productIds: productIds, operation: action]) + + then: + !payload.results + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategorySubComCodeFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategorySubComCodeFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategorySubComCodeFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,59 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec + +class CategorySubComCodeFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'subComCode' } + + final static int ALL_COLUMNS_SIZE = 13 + + Integer catalogId = 1 + Integer categoryId = 81 + Map mapping = [catalog: catalogId, category: categoryId] + + def 'can NOT find a Category SubComCode that does not exist'() { + given: + path(mapping, -1) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find a Category SubComCode'() { + given: + Integer subComCodeId = 106 + path(mapping, subComCodeId) + ok() + + when: + get() + + then: + payload.results.subComCodeId == subComCodeId + payload.results.categoryId == categoryId + payload.results.catalogInstanceId == catalogId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can find Category SubComCodes'() { + given: + path(mapping) + ok() + + when: + get() + + then: + payload.results.catalogInstanceId.every { it == catalogId } + payload.results.categoryId.every { it == categoryId } + payload.results.subComCode.every { it != null } + payload.results[0].size() == ALL_COLUMNS_SIZE + payload.results.size() > 2 + } +} Index: src/integration-test/groovy/com/lemans/ds/category/CategorySubComCodePersistenceFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/category/CategorySubComCodePersistenceFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/category/CategorySubComCodePersistenceFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,71 @@ +package com.lemans.ds.category + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Stepwise + +@Stepwise +class CategorySubComCodePersistenceFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'subComCode' } + + final static int ALL_COLUMNS_SIZE = 11 + + Integer catalogId = 1 + Integer categoryId = 81 + Integer subComCodeId = 136 + + Map mapping = [catalog: catalogId, category: categoryId] + + def 'can NOT add a mapping for a SubComCode that does not exist to a Category'() { + given: + path(mapping) + invalid() + + when: + post([subComCodeId: -1]) + + then: + payload.messages.find { it.code == 'DATA_INTEGRITY_VIOLATION' } + } + + def 'can NOT remove a CategorySubComCode mapping that does not exist to a Category'() { + given: + path(mapping, -1) + notFound() + + when: + delete() + + then: + !payload + } + + def 'can add add a mapping for a SubComCode to a Category'() { + given: + path(mapping) + ok() + + when: + post(subComCodeId: subComCodeId) + + then: + with(payload.results) { + categorySubComCodeId + subComCodeId == this.subComCodeId + categoryId == this.categoryId + } + } + + def 'can remove a mapping for a SubComCode from a Category'() { + given: + path(mapping, subComCodeId) + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/explosion/ExplosionDiagramFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/explosion/ExplosionDiagramFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/explosion/ExplosionDiagramFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,130 @@ +package com.lemans.ds.explosion + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared +import spock.lang.Stepwise + +@Stepwise +class ExplosionDiagramFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'explosionDiagram' } + + @Shared Integer explosionDiagramId + + Integer invalidId = -1 + + def 'can not add explosion diagram'() { + given: + Map input = [title: 'test', jsonData: "results:[description: 'test']"] + path() + invalid() + + when: + post(input) + + then: + payload.messages[0].type == 'error' + payload.messages[0].text == 'isActive is required' + } + + def 'can not get explosion diagram'() { + given: + path("$invalidId") + notFound() + + when: + get() + + then: + !payload.results + } + + def 'can not update explosion diagram'() { + given: + Map input = [jsonData: "results:[description: 'test update']", isActive: 1] + path("$invalidId") + notFound() + + when: + put(input) + + then: + !payload + } + + def 'can not delete explosion diagram'() { + given: + path("$invalidId") + notFound() + + when: + delete() + + then: + !payload + } + + def 'can add explosionDiagram'() { + given: + Map input = [title: 'test', jsonData: "results:[description: 'test']", isActive: 1] + path() + ok() + + when: + post(input) + explosionDiagramId = payload.results.explosionDiagramId + + then: + payload.results.jsonData == "results:[description: 'test']" + } + + def 'can get all explosion diagrams'() { + given: + path() + ok() + + when: + get() + + then: + payload + } + + def 'can get explosionDiagram'() { + given: + path("$explosionDiagramId") + ok() + + when: + get() + + then: + payload + } + + def 'can update explosionDiagram'() { + given: + Map input = [jsonData: "results:[description: 'test update']", isActive: 1] + path("$explosionDiagramId") + ok() + + when: + put(input) + + then: + payload.results.jsonData == "results:[description: 'test update']" + } + + def 'can delete explosionDiagram'() { + given: + path("$explosionDiagramId") + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/exporting/BulkUploadExportReportFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/exporting/BulkUploadExportReportFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/exporting/BulkUploadExportReportFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,64 @@ +package com.lemans.ds.exporting + +import com.lemans.ds.testing.DsFuncSpec + + +class BulkUploadExportReportFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'report' } + + + def 'can download a report for locale bulk upload'() { + given: + String reportName = 'productLocaleExport' + queryParams += [productId: '8945', + urlContext: 'https://dashboard4.stage.lemanscorp.com/digital-services/product/$param1/parts'] + path("$reportName/download") + + ok() + + when: + get() + + then: + payload.response.contentType == 'application/vnd.ms-excel' + payload.response.headers.'content-disposition'.contains 'ProductExport.xls' + } + + def 'can download a category attribute value report for locale bulk upload'() { + given: + String reportName = 'categoryAttributeLocaleExport' + queryParams += [categoryId: '101', + urlContext: 'https://dashboard4.stage.lemanscorp.com/digital-services/category/$param1/parts'] + path("$reportName/download") + + ok() + + when: + get() + + then: + payload.response.contentType == 'application/vnd.ms-excel' + payload.response.headers.'content-disposition'.contains 'CategoryAttributeValueExport.xls' + } + + def 'can download a part report for locale bulk upload'() { + given: + String reportName = 'partLocaleExport' + queryParams += [partNumber: '', exportPartList: '0001CRKICK, 0004212535, 0010, 0010401', partStatusId: '', derivedPartStatusId: '', + partDescr: '', brandCode: '', vendorId: '', vendorPartNumber: '', subComCodeId: '', qPart: '', productId: '', + categoryId: '4537', isDigiActive: '', modelId: '', startYear: '', endYear: '', attributeNameId: '', attributeValueId: '', + urlContext: 'https://dashboard4.stage.lemanscorp.com/digital-services/part/$param1/view'] + path("$reportName/download") + + ok() + + when: + get() + + then: + payload.response.contentType == 'application/vnd.ms-excel' + payload.response.headers.'content-disposition'.contains 'PartExport.xls' + } +} Index: src/integration-test/groovy/com/lemans/ds/fitment/MakeFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/fitment/MakeFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/fitment/MakeFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,80 @@ +package com.lemans.ds.fitment + +import com.lemans.ds.testing.DsFuncSpec + +class MakeFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'make' } + + final static int ALL_COLUMNS_SIZE = 10 + + def 'can find a Make'() { + given: + int id = 1 + path(id) + ok() + + when: + get() + + then: + payload.results.makeId == id + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find a Make that does not exist'() { + given: + int id = -1 + path(id) + notFound() + + when: + get() + + then: + !payload + } + + def 'can search for a Make by makeName'() { + given: + queryParams.makeName = 'john*' + path() + ok() + + when: + get() + + then: + payload.results[0].makeName.startsWith('John') + apiPaginated() == 1 + } + + def 'can search for a Make by q parameter on makeName'() { + given: + queryParams.q = 'john' + path() + ok() + + when: + get() + + then: + payload.results[0].makeName.startsWith('John') + apiPaginated() == 1 + } + + def 'can find Makes'() { + given: + queryParams.columns = 'makeId,makeName' + path() + ok() + + when: + get() + + then: + payload.results.makeId.every { it != null } + apiPaginated() > 50 + } +} Index: src/integration-test/groovy/com/lemans/ds/fitment/MakePersistenceFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/fitment/MakePersistenceFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/fitment/MakePersistenceFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,201 @@ +package com.lemans.ds.fitment + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared +import spock.lang.Stepwise +import spock.lang.Unroll + +@Stepwise +class MakePersistenceFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'make' } + + @Shared Integer makeId + + def 'can NOT create an invalid Make'() { + given: + path() + invalid() + + when: + post() + List errors = payload.messages.findAll { it.type = 'error' } + + then: + errors.find { it.field == 'makeName' } + errors.find { it.field == 'makeName' }.text == 'makeName is required' + errors.find { it.field == 'modelNameFormat' } + errors.find { it.field == 'modelNameFormat' }.text == 'modelNameFormat is required' + } + + @Unroll + def 'can NOT create a Make with invalid modelNameFormat #modelNameFormat with repeating segments'() { + given: + path() + invalid() + String makeName = 'test_' + new Date() + Map make = [makeName: makeName, modelNameFormat: modelNameFormat] + + when: + post(make) + List errors = payload.messages.findAll { it.type = 'error' } + + then: + errors.find { it.field == 'modelNameFormat' } + errors.find { it.field == 'modelNameFormat' }.text == 'Invalid Model Name Format. Segments can not be repeated.' + + where: + modelNameFormat << ['M-T-T, DT-E-E, L-C-L'] + } + + @Unroll + def 'can NOT create a Make with invalid modelNameFormat #modelNameFormat with invalid segment codes'() { + given: + path() + invalid() + String makeName = 'test_' + new Date() + Map make = [makeName: makeName, modelNameFormat: modelNameFormat] + + when: + post(make) + List errors = payload.messages.findAll { it.type = 'error' } + + then: + errors.find { it.field == 'modelNameFormat' } + errors.find { it.field == 'modelNameFormat' }.text == 'Invalid Model Name Format. Invalid segment codes present.' + + where: + modelNameFormat << ['Z-U', 'ZU', 'Z-UU', 'Z-U-U', 'Z-UUU', 'Z-UU'] + } + + def 'can NOT update an invalid Make'() { + given: + path(1) + invalid() + + when: + put([makeName: null]) + List errors = payload.messages.findAll { it.type = 'error' } + + then: + errors.find { it.field == 'makeName' } + errors.find { it.field == 'makeName' }.text == 'makeName is required' + } + + def 'can NOT delete a Make that does not exist'() { + given: + path(-1) + notFound() + + when: + delete() + + then: + !payload + } + + def 'can NOT delete a Make that has parts and models associated'() { + given: + path(5) + invalid() + + when: + delete() + + then: + with(payload) { + with(messages[0]) { + type == 'error' + text.matches('Cannot delete Make that has \\d+ parts associated') + } + with(messages[1]) { + type == 'error' + text.matches('Cannot delete Make that has \\d+ models associated') + } + } + } + + def 'can create a valid Make'() { + given: + String name = 'test_' + new Date() + Map make = [makeName: name, modelNameFormat: 'M-T-D-L-F-G'] + path() + ok() + + when: + post(make) + makeId = payload.results.makeId + + then: + with(payload.results) { + makeName == name + } + } + + def 'can update a valid Make'() { + given: + String name = 'more_test_' + new Date() + Map make = [makeName: name, modelNameFormat: 'M/T/DT/L/F/G'] + path(makeId) + ok() + + when: + put(make) + + then: + payload.results.makeId == makeId + payload.results.makeName == name + } + + @Unroll + def 'can NOT update a Make with invalid modelNameFormat #modelNameFormat with repeating segments'() { + given: + path(makeId) + invalid() + String makeName = 'test_' + new Date() + Map make = [makeName: makeName, modelNameFormat: modelNameFormat] + + when: + put(make) + List errors = payload.messages.findAll { it.type = 'error' } + + then: + errors.find { it.field == 'modelNameFormat' } + errors.find { it.field == 'modelNameFormat' }.text == 'Invalid Model Name Format. Segments can not be repeated.' + + where: + modelNameFormat << ['M-T-T', 'DT-E-E', 'L-C-L'] + } + + def 'can NOT update a Make with invalid modelNameFormat #modelNameFormat with invalid segment codes'() { + given: + path(makeId) + invalid() + String makeName = 'test_' + new Date() + Map make = [makeName: makeName, modelNameFormat: modelNameFormat] + + when: + put(make) + List errors = payload.messages.findAll { it.type = 'error' } + + then: + errors.find { it.field == 'modelNameFormat' } + errors.find { it.field == 'modelNameFormat' }.text == 'Invalid Model Name Format. Invalid segment codes present.' + + where: + modelNameFormat << ['Z-U', 'ZU', 'Z-UU', 'Z-U-U', 'Z-UUU', 'Z-UU'] + } + + def 'can delete a Make'() { + given: + path(makeId) + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/fitment/ModelFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/fitment/ModelFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/fitment/ModelFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,71 @@ +package com.lemans.ds.fitment + +import com.lemans.ds.testing.DsFuncSpec + +class ModelFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'model' } + + def 'can find a Model'() { + given: + int id = 302 + path([make: 1], id) + ok() + + when: + get() + + then: + payload.results.modelId == id + } + + def 'can NOT find a Model that does not exist'() { + given: + int id = -1 + path([make: 1], id) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find Models by make'() { + given: + path([make: 1]) + ok() + + when: + get() + + then: + payload.results[0].makeId == 1 + } + + def 'can Not find Models for invalid make'() { + given: + path([make: -1]) + ok() + + when: + get() + + then: + payload.results == [] + } + + def 'can Not find Models with out a make'() { + given: + path([make: null]) + notFound() + + when: + get() + + then: + payload.messages[0].text == 'Make is required' + } +} Index: src/integration-test/groovy/com/lemans/ds/fitment/ModelPersistenceFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/fitment/ModelPersistenceFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/fitment/ModelPersistenceFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,373 @@ +package com.lemans.ds.fitment + +import com.lemans.ds.testing.DsFuncSpec +import groovy.sql.Sql +import spock.lang.Shared +import spock.lang.Stepwise + +import java.time.Year + +@Stepwise +class ModelPersistenceFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'model' } + + @Shared Integer modelId + + + def dataSource + + def 'can NOT create an invalid Model'() { + given: + path([make: 1]) + String segmentValue = 'test_' + new Date() + invalid() + Map model = [segments: [[segmentCode: 'X', value: segmentValue]]] + + when: + post(model) + + then: + payload.messages.find { it.text == 'segment is required' } + } + + def 'can NOT create Model with invalid Make'() { + given: + path([make: -1]) + invalid() + + when: + post() + + then: + payload.messages.find { it.text == 'Make is invalid or not provided' } + } + + def 'can NOT delete a Model that does not exist'() { + given: + path([make: 1], -1) + notFound() + + when: + delete() + + then: + !payload + } + + def 'can NOT delete a Model that has parts associated to it'() { + given: + path([make: 101], 11475) + invalid() + + when: + delete() + + then: + payload.messages[0].text == 'Found Model(s) [11475] associated to parts. Cannot delete models that has parts Associated' + } + + def 'can NOT add multiple years to multiple models with invalid modelIds'() { + given: + Map model = [operation: 'ADDYEAR', modelIds: [32, 47, -1], years: [2013, 2014]] + path([make: 7]) + invalid() + + when: + post(model) + + then: + payload.messages[0].text == 'Invalid Model Ids found [-1]' + } + + def 'can NOT delete multiple models invalid modelIds'() { + given: + Map model = [operation: 'DELETE', modelIds: [-1, -2]] + path([make: 7]) + invalid() + + when: + post(model) + + then: + payload.messages[0].text == 'Invalid Model Ids found [-1, -2]' + } + + def 'can create a valid Model'() { + given: + String segmentValue = 'test_' + new Date() + Map model = [vehicleTypeId: '18001', modelNameFormat: 'E L', + segments: [[segmentCode: 'E', value: segmentValue], [segmentCode: 'L', value: segmentValue]], + years: [2001, 2002]] + + path([make: 1]) + ok() + + when: + post(model) + modelId = payload.results.modelId + + then: + with(payload.results) { + makeId == 1 + year.year == [2001, 2002] + segment.value == [segmentValue, segmentValue] + segment.segmentCode == ['L', 'E'] + } + } + + def 'can NOT create a valid Model with Empty Segment'() { + given: + Map model = [vehicleTypeId: '18001', modelNameFormat: 'E L', + segments: [], + years: [2001, 2002]] + + path([make: 1]) + invalid() + + when: + post(model) + + then: + with(payload.messages[0]) { + type == 'error' + text == 'At least one Segment is required for a Model' + } + } + + def 'can NOT create a duplicate Model'() { + given: + String segmentValue = 'test' + Map model = [vehicleTypeId: '18001', modelNameFormat: 'M T', + segments: [[segmentCode: 'M', value: segmentValue], [segmentCode: 'T', value: segmentValue]], + years: [2001, 2002]] + path([make: 1]) + invalid() + + when: + post(model) + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Model with following segments and segment Values already exists' + } + } + + def 'can add segment to a Model by segmentCode'() { + given: + Map segmentValue = [value: 'segmentValue__'] + path([make: 1], "$modelId/segment/D") + ok() + + when: + put(segmentValue) + + then: + with(payload.results) { + it.modelId == modelId + value == 'segmentValue__' + } + } + + def 'can update segment of a Model by segmentCode'() { + given: + Map segmentValue = [value: 'segmentValueEdited'] + path([make: 1], "$modelId/segment/M") + ok() + + when: + put(segmentValue) + + then: + with(payload.results) { + it.modelId == modelId + value == 'segmentValueEdited' + } + } + + def 'can NOT update segment of a Model to null by segmentCode for Model Id that one Segment Value is remaining'() { + given: + Map segmentValue = [value: null] + path([make: 13], '104/segment/M') + invalid() + + when: + put(segmentValue) + + then: + with(payload.messages[0]) { + type == 'error' + text == 'At least One Segment is required for a Model' + } + } + + def 'can update segment of a Model to null by segmentCode'() { + given: + Map segmentValue = [value: null] + path([make: 1], "$modelId/segment/M") + ok() + + when: + put(segmentValue) + + then: + with(payload.results) { + it.modelId == modelId + } + } + + def 'can NOT add multiple years to multiple models with invalid years'() { + given: + Map model = [operation: 'ADDYEAR', modelIds: [32, 47], years: [1700, 2013, 2014]] + path([make: 7]) + invalid() + + when: + post(model) + + then: + payload.messages[0].text == "Invalid Years found [1700]. Years should be between 1894 and ${Year.now().value + 1}" + } + + def 'can add multiple years to multiple models'() { + given: + Map model = [operation: 'ADDYEAR', modelIds: [32, 47], years: [2013, 2014]] + path([make: 7]) + ok() + + when: + post(model) + + then: + payload.results == [] + } + + def 'can delete multiple years to multiple models'() { + given: + Map model = [operation: 'DELETEYEAR', modelIds: [32, 47], years: [2013, 2014]] + path([make: 7]) + ok() + + when: + post(model) + + then: + payload.results == [] + } + + def 'can delete multiple models'() { + given: + Map model = [operation: 'DELETE', modelIds: [169, 2797]] + path([make: 9]) + ok() + + when: + post(model) + + then: + payload.results == [] + + cleanup: + new Sql(dataSource).execute(''' + UPDATE Model + SET dateDeleted = NULL + ,deletedBy = NULL + WHERE modelId IN (218, 223) AND dateDeleted IS NOT NULL''') + } + + def 'can NOT delete models containing parts'() { + given: + Map model = [operation: 'DELETE', modelIds: [302, 488, 603, 606, 1354]] + path([make: 1]) + invalid() + + when: + post(model) + + then: + payload.messages[0].text == 'Found Model(s) [488, 603, 606] associated to parts. Cannot delete models that has parts Associated' + } + + @SuppressWarnings(['LineLength']) + def 'can NOT update a valid Model by adding invalid years'() { + given: + Map model = [vehicleTypeId: '18001', modelNameFormat: 'M T', + segments: [[segmentCode: 'M', value: 'test_edited'], + [segmentCode: 'T', value: 'test_edited2'], [segmentCode: 'D', value: 'test_edited3']], + years: [1800, 9000]] + path([make: 1], modelId) + invalid() + + when: + put(model) + + then: + payload.messages.find { it.field != 'modelName' }.text == "Found Invalid Years [1800, 9000]. Years should be in between 1894 and ${Year.now().value + 1}" + } + + def 'can NOT update a Model with invalid segmentCodes'() { + given: + Map model = [vehicleTypeId: '18001', modelNameFormat: 'Z U P', + segments: [[segmentCode: 'Z', value: 'test_edited'], + [segmentCode: 'U', value: 'test_edited2'], [segmentCode: 'P', value: 'test_edited3']]] + path([make: 1], modelId) + invalid() + + when: + put(model) + + then: + payload.messages.find { it.field == 'modelNameFormat' }.text == 'Invalid Model Name Format. Invalid segment codes present.' + } + + def 'can update a valid Model'() { + given: + Map model = [vehicleTypeId: '18001', modelNameFormat: 'M T', + segments: [[segmentCode: 'M', value: 'test_edited'], + [segmentCode: 'T', value: 'test_edited2'], [segmentCode: 'D', value: 'test_edited3']], + years: [2011, 2015]] + path([make: 1], modelId) + ok() + + when: + put(model) + + then: + with(payload.results) { + makeId == 1 + year.year == [2001, 2002, 2011, 2015] + segment.segmentCode == ['M', 'T', 'D', 'L', 'E'] + } + } + + def 'can update a valid Model with modelNameFormat'() { + given: + Map model = [modelNameFormat: 'M T'] + path([make: 1], modelId) + ok() + + when: + put(model) + + then: + with(payload.results) { + makeId == 1 + year.year == [2001, 2002, 2011, 2015] + segment.segmentCode == ['M', 'T', 'D', 'L', 'E'] + } + } + + def 'can delete a Model'() { + given: + path([make: 1], modelId) + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/fitment/ModelYearFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/fitment/ModelYearFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/fitment/ModelYearFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,112 @@ +package com.lemans.ds.fitment + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Ignore +import spock.lang.Shared +import spock.lang.Stepwise + +@Stepwise +class ModelYearFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'year' } + + @Shared List yearIds + + @Shared List years + + def 'can NOT create an invalid Year'() { + given: + path([make: 1, model: 302]) + invalid() + + when: + post() + + then: + payload.messages.find { it.text == 'year(s) is required' } + } + + @Ignore + def 'can NOT delete a Year that does not exist'() { + given: + path([make: 1, model: 302], -1) + notFound() + + when: + delete() + + then: + !payload + } + + def 'can create a valid Year'() { + given: + Integer year = 2017 + path([make: 1, model: 302]) + ok() + + when: + post([year: year]) + + then: + payload.results.year == year + } + + def 'can add multiple years to a make'() { + given: + Map year = [years: [2015, 2016]] + path([make: 1, model: 302]) + ok() + + when: + post(year) + + then: + payload.results.size() == 2 + with (payload) { + results[0].year == 2015 + results[1].year == 2016 + } + } + + def 'can find Years'() { + given: + queryParams.columns = 'modelYearId, year' + path([make: 1, model: 302]) + ok() + + when: + get() + yearIds = payload.results.modelYearId + years = payload.results.year + + then: + payload.results.modelYearId.every { it != null } + apiPaginated() > 2 + } + + def 'can delete a Year by Id'() { + given: + path([make: 1, model: 302], yearIds[0]) + ok() + + when: + delete() + + then: + !payload + } + + def 'can delete multiple years'() { + given: + path([make: 1, model: 302]) + ok() + + when: + post([operation: 'DELETE', years: years]) + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/fitment/PartFitmentFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/fitment/PartFitmentFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/fitment/PartFitmentFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,131 @@ +package com.lemans.ds.fitment + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared +import spock.lang.Stepwise + +/** + * Created by vramisetti on 6/13/2017. + */ +@Stepwise +class PartFitmentFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'fitment' } + + @Shared List partFitmentIds = [] + + def 'can NOT find partFitments for invalid part'() { + given: + String pNum = 'B8ESSS' + path([part: pNum]) + ok() + + when: + get() + + then: + payload.meta.totalRecords == 0 + payload.results == [] + } + + def 'can add a partFitment'() { + given: + String pNum = 'B8ES' + Integer mYId = 100 + path([part: pNum]) + ok() + Map json = [modelYearId: mYId, positions: [22001, 22002, 22003, 22004]] + + when: + post(json) + partFitmentIds << payload.results.partFitmentId + + then: + with(payload.results) { + partNumber == pNum + modelYearId == mYId + } + } + + def 'can NOT add a partFitment with invalid modelYearId'() { + given: + String pNum = 'B8ES' + Integer mYId = -1 + path([part: pNum]) + invalid() + Map json = [modelYearId: mYId] + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Invalid model year\'s found [-1]' + } + } + + def 'can find partFitments'() { + given: + String pNum = 'B8ES' + path([part: pNum]) + ok() + + when: + get() + + then: + payload + } + + def 'can delete a partFitment'() { + given: + String partNumber = 'B8ES' + Integer partFitmentId = partFitmentIds[0] + path([part: partNumber], partFitmentId) + ok() + + when: + delete() + + then: + !payload + } + + def 'can add multiple partFitments'() { + given: + String pNum = 'B8ES' + Integer mYId = 96990 + path([part: pNum]) + ok() + Map json = [modelYearIds: [mYId, 96991, 96992], + positions: [22001, 22002]] + + when: + post(json) + payload.results.each { + partFitmentIds << it.partFitmentId + } + + then: + with(payload) { + results.every { it.partNumber == pNum } + results.modelYearId == [96990, 96991, 96992] + } + } + + def 'can delete multiple partFitments'() { + given: + String partNumber = 'B8ES' + path([part: partNumber]) + ok() + Map json = [partFitmentIds: partFitmentIds, operation: 'DELETE'] + + when: + post(json) + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/fitment/SegmentFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/fitment/SegmentFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/fitment/SegmentFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,66 @@ +package com.lemans.ds.fitment + +import com.lemans.ds.testing.DsFuncSpec + +class SegmentFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { '' } + + def 'can find Segments'() { + given: + queryParams.columns = 'segmentId,segmentName,segmentCode' + path([make: 1], 'segment') + ok() + + when: + get() + + then: + payload.results.segmentId.every { it != null } + apiPaginated() > 3 + } + + def 'can find Segment values by segmentCodes'() { + given: + path([make: 1, segment: 'M'], 'value') + ok() + + when: + get() + + then: + payload.results.segmentId.every { it != null } + apiPaginated() > 3 + } + + def 'can search Segment values '() { + given: + queryParams.value = 'MX*' + path([make: 10, segment: 'M'], 'value') + ok() + + when: + get() + + then: + payload.results.segmentId.every { it != null } + payload.results.value.every { it.startsWith('MX') } + apiPaginated() >= 1 + } + + def 'can search Segment values by q parameter'() { + given: + queryParams.q = 'Model' + path([make: 1, segment: 'M'], 'value') + ok() + + when: + get() + + then: + payload.results.segmentId.every { it != null } + payload.results.value.every { it.contains('Model') } + apiPaginated() > 3 + } +} Index: src/integration-test/groovy/com/lemans/ds/flag/FlagMediaEntityFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/flag/FlagMediaEntityFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/flag/FlagMediaEntityFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,76 @@ +package com.lemans.ds.flag + +import com.lemans.ds.testing.DsFuncSpec + +class FlagMediaEntityFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'flag' } + + def 'can create flagValue with mediaId'() { + given: + Map input = [flagIds: [23], operation: 'INSERT'] + path([media: 3]) + queryParams.entityClass = 'Media' + ok() + + when: + post(input) + + then: + payload + } + + def 'can not create flagValue with invalid mediaId'() { + given: + Map input = [flagIds: [23], operation: 'INSERT'] + path([media: -3]) + queryParams.entityClass = 'Media' + invalid() + + when: + post(input) + + then: + payload.messages[0].type == 'error' + payload.messages[0].text == 'Media with mediaId -3 does not exist' + } + + def 'can get flags for media'() { + given: + path([media: 3]) + ok() + + when: + get() + + then: + payload + } + + def 'can get flagvalue for a media and flag'() { + given: + path([media: 3], 23) + ok() + + when: + get() + + then: + payload + } + + def 'can delete flagValue with mediaId'() { + given: + path([media: 3]) + Map input = [flagIds: [23], operation: 'DELETE'] + queryParams.entityClass = 'Media' + ok() + + when: + post(input) + + then: + payload + } +} Index: src/integration-test/groovy/com/lemans/ds/flag/FlagPartEntityPersistenceFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/flag/FlagPartEntityPersistenceFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/flag/FlagPartEntityPersistenceFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,246 @@ +package com.lemans.ds.flag + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Stepwise + +@Stepwise +class FlagPartEntityPersistenceFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'flag' } + + final static int ALL_COLUMNS_SIZE = 12 + + + String invalidPartNumber = 'xyz' + String addedPartNumber = '01040073' + Integer flagId1 = 1 + Integer flagId2 = 2 + Integer invalidFlagId = 6 + Integer invalidFlagId1 = 8 + + def 'can NOT add invalid Flag to validPart'() { + given: + path([part: addedPartNumber]) + invalid() + Map input = [ + flagIds: [invalidFlagId1, invalidFlagId], + operation: 'INSERT' + ] + + when: + post(input) + + then: + with(payload) { + messages[0].type == 'error' + messages[0].text == 'Invalid FlagIds found [8, 6]' + } + } + + def 'can NOT add Flag to invalidPart'() { + given: + path([part: invalidPartNumber]) + invalid() + Map input = [ + flagIds: [flagId1, flagId2], + operation: 'INSERT' + ] + + when: + post(input) + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Part with partNumber xyz does not exist' + } + } + + def 'can NOT add Invalid Flag to invalid Part'() { + given: + path([part: invalidPartNumber]) + invalid() + Map input = [ + flagIds: [flagId1, invalidFlagId], + operation: 'INSERT' + ] + + when: + post(input) + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Invalid FlagIds found [6]' + } + with(payload.messages[1]) { + type == 'error' + text == 'Part with partNumber xyz does not exist' + } + } + + def 'can add flagPart relation '() { + given: + path([part: addedPartNumber]) + ok() + Map input = [ + flagIds: [flagId1, flagId2], + ] + + when: + post(input) + + then: + !payload.results + } + + def 'can find allowable flags for an entity '() { + given: + path() + queryParams.entityClass = 'part' + ok() + + when: + get() + + then: + payload.results.flagId.contains(flagId1) + payload.results[0].size() == ALL_COLUMNS_SIZE + payload.results.flagId.every { it != null } + } + + def 'can find flagPart relation '() { + given: + path([part: addedPartNumber]) + ok() + + when: + get() + + then: + payload.results.flagId.contains(flagId1) + payload.results[0].size() == ALL_COLUMNS_SIZE + payload.results.flagId.every { it != null } + } + + def 'can find flagPart relation for a part with no flag'() { + given: + path([part: '004165'], flagId1) + notFound() + + when: + get() + + then: + !payload + } + + def 'can show flagValue with entityId and flagId'() { + given: + path([part: addedPartNumber], flagId1) + ok() + + when: + get() + + then: + payload.results.flagId == flagId1 + payload.results.entityId == '01040073' + } + + def 'can not show flagValue with invalid entityId'() { + given: + path([part: invalidPartNumber], flagId1) + notFound() + + when: + get() + + then: + !payload + } + + def 'can not show flagValue with invalid flagId'() { + given: + Integer id = -1 + path([part: 'B8es'], id) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT delete if there is an invalid flag'() { + given: + String partNumber = addedPartNumber + path([part: partNumber]) + Map input = [ + flagIds: [flagId1, invalidFlagId], + operation: 'DELETE' + ] + invalid() + + when: + post(input) + + then: + payload.messages[0].text == 'Invalid FlagIds found [6]' + } + + def 'can delete'() { + given: + path([part: addedPartNumber]) + Map input = [ + flagIds: [flagId1, flagId2], + operation: 'DELETE' + ] + ok() + + when: + post(input) + + then: + payload.results == [] + } + + def 'can still delete if invalid flag is included'() { + given: + path([part: '002-05901']) + Map input = [ + flagIds: [invalidFlagId, flagId1], + operation: 'DELETE' + ] + invalid() + + when: + post(input) + + then: + payload.messages[0].text == 'Invalid FlagIds found [6]' + } + + def 'can not delete if no flags are selected'() { + given: + String partNumber = addedPartNumber + path([part: partNumber]) + Map input = [ + flagIds: [], + operation: 'DELETE' + ] + invalid() + + when: + post(input) + + then: + with(payload.messages[0]) { + type == 'error' + text == 'No FlagIds found' + } + + } +} Index: src/integration-test/groovy/com/lemans/ds/flag/FlagProductEntityFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/flag/FlagProductEntityFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/flag/FlagProductEntityFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,98 @@ +package com.lemans.ds.flag + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Stepwise + +@Stepwise +class FlagProductEntityFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'flag' } + + final static int ALL_COLUMNS_SIZE = 12 + + + String invalidProductNumber = 'xyz' + String addedProductNumber = '316931' + Integer flagId1 = 11 + + def 'can find allowable flags for an entity '() { + given: + path() + queryParams.entityClass = 'Product' + ok() + + when: + get() + + then: + payload.results.flagId.contains(flagId1) + payload.results[0].size() == ALL_COLUMNS_SIZE + payload.results.flagId.every { it != null } + } + + def 'can find flagProduct relation '() { + given: + path([product: addedProductNumber]) + ok() + + when: + get() + + then: + payload.results.flagId.contains(flagId1) + payload.results[0].size() == ALL_COLUMNS_SIZE + payload.results.flagId.every { it != null } + } + + def 'can show flagValue with entityId and flagId'() { + given: + path([product: addedProductNumber], flagId1) + ok() + + when: + get() + + then: + payload.results.flagId == flagId1 + payload.results.entityId == '316931' + } + + def 'can show flag fo finalized product'() { + given: + path([product: addedProductNumber], flagId1) + ok() + + when: + get() + + then: + payload.results.flagId == flagId1 + payload.results.entityId == '316931' + } + + def 'can not show flagValue with invalid entityId'() { + given: + path([product: invalidProductNumber], flagId1) + notFound() + + when: + get() + + then: + !payload + } + + def 'can not show flagValue with invalid flagId'() { + given: + Integer id = -1 + path([product: 'B8es'], id) + notFound() + + when: + get() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/importing/LocaleBulkFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/importing/LocaleBulkFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/importing/LocaleBulkFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,54 @@ +package com.lemans.ds.importing + +import com.lemans.ds.testing.DsFuncSpec + +/** + * Created by mumachi on 12/6/2017. + */ +class LocaleBulkFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'localeImportJob' } + + final static int ALL_COLUMNS_SIZE = 15 + + def 'can find Locale Import data by localeImportProcessId'() { + given: + int importFileId = 6 + path(importFileId) + ok() + + when: + get() + + then: + payload.results.localeImportProcessId == importFileId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT find Locale Import by id that does not exist'() { + given: + path(-1) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find All Locale Import'() { + given: + path() + ok() + + when: + get() + + then: + payload.results.localeImportProcessId.every { it } + payload.results[0].size() == ALL_COLUMNS_SIZE + apiPaginated() > 5 + } +} Index: src/integration-test/groovy/com/lemans/ds/importing/LocaleBulkImportFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/importing/LocaleBulkImportFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/importing/LocaleBulkImportFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,217 @@ +package com.lemans.ds.importing + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Ignore +import spock.lang.Shared +import spock.lang.Stepwise + +import java.nio.file.Files + +@Stepwise +class LocaleBulkImportFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'localeImportJob' } + + private static final String TEMP_MEDIA_FOLDER = 'temporaryMediaPath/' + + @Shared String fileName = 'testForNoProductId' + @Shared File temporaryMediaFolder = new File(TEMP_MEDIA_FOLDER) + @Shared File testFile = new File('files/' + fileName + '.xlsx') + @Shared File temporaryFile = new File(TEMP_MEDIA_FOLDER + fileName + '.xlsx') + @Shared Integer importFileId + + def setupSpec() { + temporaryMediaFolder.mkdir() + assert testFile.exists() + if (!temporaryFile.exists()) { Files.copy(testFile.toPath(), temporaryFile.toPath()) } + assert temporaryFile.exists() + } + + def cleanupSpec() { + temporaryFile.delete() + assert !temporaryFile.exists() + temporaryMediaFolder.delete() + assert testFile.exists() + } + + def 'can NOT add an Import file without tempId'() { + given: + path() + Map body = [ + originalFileName: fileName, + extension: 'xlsx', + importType: 'Product' + ] + invalid() + + when: + post(body) + + then: + payload.messages.find { it.text == 'tempId is required' } + } + + def 'can NOT add an Import file without the originalFileName'() { + given: + path() + Map body = [ + extension: 'xlsx', + importFileId: importFileId, + importType: 'Product' + ] + invalid() + + when: + post(body) + + then: + payload.messages.find { it.text == 'tempId is required' } + } + + def 'can NOT add an Import File without an extension'() { + given: + path() + invalid() + + when: + post([tempId: fileName, importType: 'Product', originalFileName: fileName]) + + then: + payload.messages.find { it.text == 'Extension is required' } + } + + def 'can add an Import File for Product'() { + given: + Map body = [ + originalFileName: 'testForNoProductId', + extension: 'xlsx', + tempId: fileName, + importType: 'Product', + ] + path() + ok() + + when: + post(body) + importFileId = payload.results.localeImportProcessId + + then: + importFileId + payload.results.originalFileName + payload.results.originalFileName == 'testForNoProductId' + Thread.sleep(10000) //To Make sure the Job is finished to download those files. + } + + @Ignore + def 'can add an Import File for CategoryAttribute'() { + given: + Map body = [ + originalFileName: 'CategoryAttributeValue', + extension: 'xlsx', + tempId: fileName, + importType: 'CategoryAttribute', + ] + path() + ok() + + when: + post(body) + importFileId = payload.results.localeImportProcessId + + then: + importFileId + payload.results.originalFileName + payload.results.originalFileName == 'CategoryAttributeValue' + Thread.sleep(15000) //To Make sure the Job is finished to download those files. + } + + @Ignore + def 'can add an Import File for Part'() { + given: + Map body = [ + originalFileName: 'PartExport', + extension: 'xlsx', + tempId: fileName, + importType: 'Part', + ] + path() + ok() + + when: + post(body) + importFileId = payload.results.localeImportProcessId + + then: + importFileId + payload.results.originalFileName + payload.results.originalFileName == 'PartExport' + Thread.sleep(10000) //To Make sure the Job is finished to download those files. + } + + def 'can NOT delete an Import File that does not exist'() { + given: + path(-1) + invalid() + + when: + delete() + + then: + payload.messages[0].text == "Process can't be deleted, until it is completely processed." + } + + def 'can download Original File'() { + given: + String fileId = 'Original' + path("$importFileId/file/$fileId/download") + ok() + + when: + get() + + then: + payload.response.headers.'Content-disposition'.endsWith 'testForNoProductId.xlsx' + payload.response.contentType == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + } + + def 'can NOT download Report File that does NOT exists'() { + given: + String fileId = 'Report' + path("$importFileId/file/$fileId/download") + notFound() + + when: + get() + + then: + !payload + } + + @Ignore + def 'can download Report File if it exists'() { + given: + String fileId = 'Report' + path("$importFileId/file/$fileId/download") + ok() + + when: + get() + + then: + payload.response.headers.'Content-disposition'.endsWith 'Report.xls' + payload.response.contentType == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + } + + def 'can delete an Import File'() { + given: + path(importFileId) + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/part/BulkPartAttributeFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/part/BulkPartAttributeFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/part/BulkPartAttributeFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,268 @@ +package com.lemans.ds.part + +import com.lemans.ds.testing.DsFuncSpec + +class BulkPartAttributeFuncSpec extends DsFuncSpec { + + + final static int ALL_COLUMNS_SIZE = 21 + + String partNumber1 = '010110074' + String partNumber2 = '25011551' + Integer attributeNameId = 5793 + Integer attributeValueId = 101059 + + def 'can NOT perform bulk operation on partAttribute without partNumber'() { + given: + String json = """ +{ +"operation": "INSERT", +"attribute":[ +{ +"attributeNameId": $attributeNameId, +"attributeValueIds": [$attributeValueId] +} +] +} +""" + path('part/attribute') + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + text.contains('No Part Selected') + } + } + + def 'can NOT perform bulk operation on partAttribute without attributeName'() { + given: + String json = """ +{ +"partNumber": ["$partNumber1", "xxyz"], +"operation": "INSERT", +"attribute":[ +] +} +""" + path('part/attribute') + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + text.contains('No attribute change selected') + } + } + + def 'can NOT perform bulk operation on partAttribute without attributeValue'() { + given: + String json = """ +{ +"partNumber": ["$partNumber1"], +"operation": "INSERT", +"attribute":[ +{ +"attributeNameId": $attributeNameId, +"attributeValueIds": null +} +] +} +""" + path('part/attribute') + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + text.contains('Invalid attribute value') + } + } + + def 'can NOT perform bulk operation on partAttribute for invalidPart'() { + given: + String json = """ +{ +"partNumber": ["$partNumber1", "xxyz"], +"operation": "INSERT", +"attribute":[ +{ +"attributeNameId": $attributeNameId, +"attributeValueIds": [$attributeValueId] +} +] +} +""" + path('part/attribute') + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + text.contains('Invalid Part') + } + + } + + + def 'can NOT perform bulk add partAttribute for invalid attributeValueId'() { + given: + String json = """ +{ +"partNumber": ["$partNumber1"], +"operation": "INSERT", +"attribute":[ +{ +"attributeNameId": $attributeNameId, +"attributeValueIds": [-1] +} +] +} +""" + path('part/attribute') + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + text.contains('Invalid attribute') + } + + } + + def 'can NOT perform bulk add partAttribute for part and attribute not related to same category'() { + given: + String json = """ +{ +"partNumber": ["012"], +"operation": "INSERT", +"attribute":[ +{ +"attributeNameId": $attributeNameId, +"attributeValueIds": [$attributeValueId] +} +] +} +""" + path('part/attribute') + invalid() + + when: + post(json) + + then: + with(payload.messages[1]) { + type == 'error' + text.contains('not belong to same category') + } + + } + + def 'can NOT perform bulk add partAttribute for part does not assigned to valid category'() { + given: + String json = """ +{ +"partNumber": ["38070158"], +"operation": "INSERT", +"attribute":[ +{ +"attributeNameId": $attributeNameId, +"attributeValueIds": [$attributeValueId] +} +] +} +""" + path('part/attribute') + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + text.contains('Category is not assigned') + } + + } + def 'can bulk delete partAttribute for Parts'() { + given: + String json = """ +{ +"partNumber": ["b8es", "b9es"], +"operation": "DELETE", +"attribute":[ +{ +"attributeNameId": $attributeNameId, +"attributeValueIds": [$attributeValueId] +} +] +} +""" + path('part/attribute') + ok() + + when: + post(json) + + then: + !payload.results + } + + def 'can bulk add partAttribute for Parts'() { + given: + String json = """ +{ +"partNumber": ["01104390", "01104391"], +"operation": "INSERT", +"attribute":[ +{ +"attributeNameId": $attributeNameId, +"attributeValueIds": [$attributeValueId] +} +] +} +""" + path('part/attribute') + ok() + + when: + post(json) + + then: + !payload.results + } + + def 'can bulk REPLACE partAttribute for Parts'() { + given: + String json = """ +{ +"attributeValueIds": [$attributeValueId] +} +""" + path([part: "$partNumber1", attribute: attributeNameId], 'value') + ok() + + when: + put(json) + + then: + !payload.results + } + +} Index: src/integration-test/groovy/com/lemans/ds/part/PartAssociationFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/part/PartAssociationFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/part/PartAssociationFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,215 @@ +package com.lemans.ds.part + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared + +class PartAssociationFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'part' } + + private static final String PART_NUMBER_1 = 'b8es' + + private static final String PART_NUMBER_2 = 'b9es' + + private static final String RELATED_PART_1 = '012' + + private static final String RELATED_PART_2 = '0001CRKICK' + + private static final String RELATED_PART_3 = 'b8es' + + private static final Integer ASSOCIATION_TYPE_ID_1 = 20001 + + private static final Integer ASSOCIATION_TYPE_ID_2 = 20002 + + @Shared + Integer partAssociationId1 + + @Shared + Integer partAssociationId2 + + @Shared + Integer partAssociationId3 + + def 'can not find related part'() { + given: + path('_invalid_/relatedPart') + ok() + + when: + get() + + then: + payload.meta.totalRecords == 0 + } + + def 'can not find referral part'() { + given: + path('_invalid_/referralPart') + ok() + + when: + get() + + then: + payload.meta.totalRecords == 0 + } + + def 'can not add partAssociation'() { + given: + Map inputPayload = [relatedPartNumber: RELATED_PART_1] + path("$PART_NUMBER_1/relatedPart") + invalid() + + when: + post(inputPayload) + + then: + payload.messages[0].field == 'associationTypeId' + payload.messages[0].text == 'associationTypeId is required' + } + + def 'can add partAssociation example1'() { + given: + Map inputPayload = [associationTypeId: ASSOCIATION_TYPE_ID_1, relatedPartNumber: RELATED_PART_1] + path("$PART_NUMBER_1/relatedPart") + ok() + + when: + post(inputPayload) + partAssociationId1 = payload.results.partAssociationId + + then: + payload.results.associationTypeId == ASSOCIATION_TYPE_ID_1 + payload.results.relatedPartNumber == RELATED_PART_1 + } + + def 'can not add duplicate partAssociation example1'() { + given: + Map inputPayload = [associationTypeId: ASSOCIATION_TYPE_ID_1, relatedPartNumber: RELATED_PART_1] + path("$PART_NUMBER_1/relatedPart") + invalid() + + when: + post(inputPayload) + + then: + payload.messages[0].type == 'error' + payload.messages[0].text == "PartAssociation for partNumber $PART_NUMBER_1 with " + + "associationTypeId $ASSOCIATION_TYPE_ID_1 and relatedPartNumber $RELATED_PART_1 exists." + } + + def 'can add partAssociation example2'() { + Map inputPayload = [associationTypeId: ASSOCIATION_TYPE_ID_1, relatedPartNumber: RELATED_PART_2] + path("$PART_NUMBER_1/relatedPart") + ok() + + when: + post(inputPayload) + partAssociationId2 = payload.results.partAssociationId + + then: + payload.results.associationTypeId == ASSOCIATION_TYPE_ID_1 + payload.results.relatedPartNumber == RELATED_PART_2 + } + + def 'can add partAssociation example3'() { + Map inputPayload = [associationTypeId: ASSOCIATION_TYPE_ID_1, relatedPartNumber: RELATED_PART_3] + path("$PART_NUMBER_2/relatedPart") + ok() + + when: + post(inputPayload) + partAssociationId3 = payload.results.partAssociationId + + then: + payload.results.associationTypeId == ASSOCIATION_TYPE_ID_1 + payload.results.relatedPartNumber == RELATED_PART_3 + } + + def 'can find related part'() { + given: + path("$PART_NUMBER_1/relatedPart") + ok() + + when: + get() + + then: + payload.meta.totalRecords >= 1 + payload.results.partNumber.every { it == PART_NUMBER_1 } + } + + def 'can find referral part'() { + given: + path("$PART_NUMBER_1/referralPart") + ok() + + when: + get() + + then: + payload.meta.totalRecords >= 1 + payload.results[1].relatedPartNumber == PART_NUMBER_1 + } + + def 'can get a partAssociation'() { + path("$PART_NUMBER_1/relatedPart/$partAssociationId1") + ok() + + when: + get() + + then: + payload.results.associationTypeId == ASSOCIATION_TYPE_ID_1 + } + + def 'can update partAssociation'() { + given: + Map inputPayload = [associationTypeId: ASSOCIATION_TYPE_ID_2] + path("$PART_NUMBER_1/relatedPart/$partAssociationId1") + ok() + + when: + put(inputPayload) + + then: + payload.results.associationTypeId == ASSOCIATION_TYPE_ID_2 + } + + def 'can delete partAssociation example1'() { + given: + path("$PART_NUMBER_1/relatedPart/$partAssociationId1") + ok() + + when: + delete() + + then: + !payload + } + + def 'can delete partAssociation example2'() { + given: + path("$PART_NUMBER_1/relatedPart/$partAssociationId2") + ok() + + when: + delete() + + then: + !payload + } + + def 'can delete partAssociation example3'() { + given: + path("$PART_NUMBER_2/relatedPart/$partAssociationId3") + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/part/PartAttributeDetailsFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/part/PartAttributeDetailsFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/part/PartAttributeDetailsFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,78 @@ +package com.lemans.ds.part + +import com.lemans.ds.testing.DsFuncSpec + +class PartAttributeDetailsFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'part/details' } + + final static int ALL_COLUMNS_SIZE = 21 + + def 'can NOT find a PartAttribute for a Catalog that does not exist'() { + given: + path([catalog: 0, category: -1]) + notFound() + + when: + post() + + then: + !payload + } + + def 'can find a Category details for a valid Catalog'() { + given: + int categoryId = 592 + String json = ''' + { + "partNumber":["B10ES", "01000003"] + }''' + path([catalog: 0, category: categoryId]) + ok() + + when: + post(json) + + then: + payload.results.categoryId == categoryId + payload.results.part.size() == 2 + } + + def 'can find a Category details for a valid Catalog for a valid Locale'() { + given: + int categoryId = 592 + optionalHeaders.locale = 'de' + String json = ''' + { + "partNumber":["B10ES", "01000003"] + }''' + path([catalog: 0, category: categoryId]) + ok() + + when: + post(json) + + then: + payload.results.categoryId == categoryId + payload.results.part.size() == 2 + } + + def 'can NOT find a Category details for a valid Catalog for an invalid Locale'() { + given: + int categoryId = 592 + optionalHeaders.locale = 'xy' + String json = ''' + { + "partNumber":["B10ES", "01000003"] + }''' + path([catalog: 0, category: categoryId]) + notFound() + + when: + post(json) + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/part/PartAttributeFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/part/PartAttributeFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/part/PartAttributeFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,62 @@ +package com.lemans.ds.part + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Ignore + + +class PartAttributeFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'value' } + + final static int ALL_COLUMNS_SIZE = 21 + + String partNumber = '25011550' + Integer attributeNameId = 5793 + Integer attributeValueId = 101059 + + def 'can NOT add attribute for invalidPart'() { + given: + String json = """{"attributeValueId": $attributeValueId}""" + path([part: 'xxyyz', attribute: attributeNameId]) + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + text.contains('Invalid Part') + } + } + + @Ignore + def 'can add partAttribute relation '() { //TODO: unIgnore + given: + String partNumber = '06010006' + Integer attributeNameId = 5436 + Integer attributeValueId = 100515 + String json = """{"attributeValueId": $attributeValueId}""" + path([part: "$partNumber", attribute: attributeNameId]) + ok() + + when: + post(json) + + then: + !payload.results + } + + def 'can delete partAttribute relation '() { + given: + path([part: "$partNumber", attribute: attributeNameId], attributeValueId) + ok() + + when: + delete() + + then: + !payload.results + } +} Index: src/integration-test/groovy/com/lemans/ds/part/PartFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/part/PartFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/part/PartFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,382 @@ +package com.lemans.ds.part + +import com.lemans.ds.testing.DsFuncSpec +import com.lemans.ds.PartService + +class PartFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'part' } + + final static int ALL_COLUMNS_SIZE = 102 + + final static int SEARCH_COLUMNS_SIZE = PartService.SEARCH_COLUMNSET.size() + + def 'can NOT find Parts without a pageSize param'() { + given: + queryParams.pageSize = null + path() + invalid() + + when: + get() + + then: + payload.messages.find { it.text == 'pageSize is required' } + } + + def 'can NOT find a Part by partNumber that does not exist'() { + given: + path('____') + notFound() + + when: + get() + + then: + !payload + } + + def 'can find Parts containing a partial partNumber'() { + given: + String partialPartNumber = '8E' + String partNumber = "*$partialPartNumber*" + queryParams.partNumber = partNumber + path() + ok() + + when: + get() + + then: + payload.results.partNumber.every { it.contains partialPartNumber } + apiPaginated() > 23 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts containing a partial partNumber OR partDescr'() { + given: + String q = 'KICK' + queryParams.q = q + path() + ok() + + when: + get() + List partNumberMatches = payload.results.findAll { it.partNumber.contains(q) } + List partDescrMatches = payload.results.findAll { it.partDescr.contains(q) } + + then: + payload.results.every { + it.partNumber.contains(q) || it.partDescr.contains(q) + } + partNumberMatches + partDescrMatches + payload.results[0].size() == SEARCH_COLUMNS_SIZE + apiPaginated() > 200 + apiPaginated() < 1000 + } + + def 'can find a Part by partNumber'() { + given: + queryParams.columns = null + String partNumber = '02151001' + path(partNumber) + ok() + + when: + get() + + then: + payload.results.partNumber == partNumber + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can find Standard Parts'() { + given: + String partStatusCode = 'S' + queryParams.partStatusCode = partStatusCode + path() + ok() + + when: + get() + + then: + payload.results.partStatusCode.every { it == partStatusCode } + apiPaginated() > 73000 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts by Category'() { + given: + Integer categoryId = 592 + queryParams.categoryId = categoryId + path() + ok() + + when: + get() + + then: + payload.results.categoryId.every { it == categoryId } + apiPaginated() > 1 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts not assigned to a Category'() { + given: + queryParams.categoryId = Part.UNASSIGNED_CATEGORY_ID + path() + ok() + + when: + get() + + then: + payload.results.categoryId.every { it == null } + apiPaginated() + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts containing a partial partDescription'() { + given: + String partialPartDescription = 'EBONY' + String partDescription = "*$partialPartDescription*" + queryParams.partDescr = partDescription + path() + ok() + + when: + get() + + then: + payload.results.partDescr.every { it.contains partialPartDescription } + apiPaginated() > 30 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts for single subComCode'() { + given: + String subComCode = '0101' + queryParams.subComCode = subComCode + path() + ok() + + when: + get() + + then: + payload.results.subComCode.every { it == subComCode } + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts for multiple SubComCodes'() { + given: + String code1 = '0101' + String code2 = '0102' + queryParams.subComCode = "$code1,$code2" + path() + ok() + + when: + get() + + then: + payload.results.subComCode.every { it == code1 || it == code2 } + apiPaginated() > 0 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts for single productId'() { + given: + String productId = '343229' + queryParams.productId = productId + path() + ok() + + when: + get() + + then: + apiPaginated() > 1 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts for multiple productId'() { + given: + String productId1 = '343229' + String productId2 = '123' + queryParams.productId = "$productId1,$productId2" + path() + ok() + + when: + get() + + then: + apiPaginated() > 1 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts unassigned to a product'() { + given: + String productId = Part.UNASSIGNED_PRODUCT_ID + queryParams.productId = productId + path() + ok() + + when: + get() + + then: + apiPaginated() > 1000 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts referencing a partial brandCode'() { + given: + String partialBrandCode = '068' + String brandCode = "*$partialBrandCode" + queryParams.brandCode = brandCode + path() + ok() + + when: + get() + + then: + payload.results.brandCode.every { it.contains partialBrandCode } + apiPaginated() > 530 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts referencing a partial brandName'() { + given: + String partialBrandName = 'MA' + String brandName = "*$partialBrandName" + queryParams.brandName = brandName + path() + ok() + + when: + get() + + then: + payload.results.brandName.every { it.contains partialBrandName } + apiPaginated() > 150 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts referencing a partial vendorName'() { + given: + String partialVendorName = 'AFX' + String vendorName = "$partialVendorName*" + queryParams.vendorName = vendorName + path() + ok() + + when: + get() + + then: + payload.results.vendorName.every { it.contains partialVendorName } + apiPaginated() > 4900 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts referencing an exact vendorName'() { + given: + String vendorName = 'AFX NORTH AMERICA INC' + queryParams.vendorName = vendorName + path() + ok() + + when: + get() + + then: + payload.results.vendorName.every { it == vendorName } + apiPaginated() > 4900 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts referencing a partial vendorId'() { + given: + String partialVendorId = '00A' + String vendorId = "*$partialVendorId" + queryParams.vendorId = vendorId + path() + ok() + + when: + get() + + then: + payload.results.vendorId.every { it.contains partialVendorId } + apiPaginated() > 300 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can find Parts containing a partial vendorPartNumber'() { + given: + String partialVendorPartNumber = '113' + String vendorPartNumber = "$partialVendorPartNumber*" + queryParams.vendorPartNumber = vendorPartNumber + path() + ok() + + when: + get() + + then: + payload.results.vendorPartNumber.every { it.contains partialVendorPartNumber } + apiPaginated() > 500 + payload.results[0].size() == SEARCH_COLUMNS_SIZE + } + + def 'can not find part by invalid partNumber and locale'() { + given: + String partNumber = '_invalid_' + queryParams.locale = 'de' + path(partNumber) + notFound() + + when: + get() + + then: + !payload + } + + def 'can not find part by partNumber with invalid locale'() { + given: + String partNumber = 'b8es' + queryParams.locale = '_invalid_' + path(partNumber) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find part by partNumber and locale'() { + given: + String partNumber = '0001CRKICK' + queryParams.locale = 'de' + path(partNumber) + ok() + + when: + get() + + then: + with(payload.results) { + locale == 'de' + partNumber + } + } +} Index: src/integration-test/groovy/com/lemans/ds/part/PartPersistenceFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/part/PartPersistenceFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/part/PartPersistenceFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,218 @@ +package com.lemans.ds.part + +import com.lemans.ds.testing.DsFuncSpec + + +class PartPersistenceFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'part' } + + final static int ALL_COLUMNS_SIZE = 102 + + def 'can NOT update a Part that does not exist'() { + given: + String partNumber = 'XXYYZZAABB' + String json = '{ "marketingDescr": "Updated" }' + path(partNumber) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can NOT update an Part with invalid categoryId'() { + given: + Map part = [categoryId: -1] + path('B8ES') + invalid() + + when: + put(part) + List errors = payload.messages.findAll { it.type = 'error' } + + then: + errors.find { it.field == 'categoryId' } + } + + def 'can not update a part with invalid primaryMediaId'() { + given: + Map part = [primaryMediaId: -1] + path('B8ES') + invalid() + + when: + put(part) + List errors = payload.messages.findAll { it.type = 'error' } + + then: + errors.find { it.field == 'primaryMediaId' } + } + + def 'can update a Part'() { + given: + String partNumber = '0001CRKICK' + String oem = 'OEM0001CRKICK' + Integer mediaId = 228030 + String partSpecificText = '' + String internalNotes = '' + String relatedParts = 'NewInsert' + String relatedProducts = '' + String certificationUS = 'test for US certificate' + String certificationEU = 'test for EU certificate' + Map part = [oemPartNumber: oem, primaryMediaId: mediaId, partSpecificText: partSpecificText, + internalNotes: internalNotes, relatedParts: relatedParts, relatedProducts: relatedProducts, + certificationUS: certificationUS, certificationEU: certificationEU] + path(partNumber) + ok() + + when: + put(part) + + then: + payload.results.partNumber == partNumber + payload.results.certificationUS == certificationUS + payload.results.certificationEU == certificationEU + payload.results.oemPartNumber == oem + payload.results.primaryMediaId == mediaId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can NOT create part and product relation with invalid productId' () { + given: + Integer productId = -1 + String partNumber = '02151001' + String json = """{ + "productId": $productId + }""" + path(partNumber) + invalid() + + when: + put(json) + + then: + with(payload.messages[0]) { + type == 'error' + text.contains 'Invalid product' + } + } + + def 'can create part and product relation' () { + given: + Integer productId = 343229 + String partNumber = '02151001' + String json = """{ + "productId": $productId + }""" + path(partNumber) + ok() + + when: + put(json) + + then: + payload.results.primaryProductId == productId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can remove part and product relation' () { + given: + String productId = '' + String partNumber = '02151001' + String json = """{ + "productId": "$productId" + }""" + path(partNumber) + ok() + + when: + put(json) + + then: + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can not update partLocale with invalid partNumber'() { + given: + String partNumber = '_invalid_' + queryParams.locale = 'de' + String json = '''{ + "specialInstructions": "testForUpdate" + }''' + path(partNumber) + notFound() + + when: + put(json) + + then: + !payload + } + + def 'can not update partLocale with invalid locale'() { + given: + String partNumber = '0001CRKICK' + queryParams.locale = '_invalid_' + String json = '''{ + "specialInstructions": "testForUpdate" + }''' + path(partNumber) + invalid() + + when: + put(json) + + then: + with(payload.messages[1]) { + type == 'error' + text == 'locale with value [_invalid_] is not contained within the list [[de, it, es, fr]]' + } + } + + def 'can update partLocale'() { + given: + String partNumber = '0001CRKICK' + queryParams.locale = 'es' + String json = '''{ + "specialInstructions": "testForUpdate1", + "partSpecificText": "testForUpdate" + }''' + path(partNumber) + ok() + + when: + put(json) + + then: + with(payload.results) { + partNumber + locale == 'es' + specialInstructionsLocale == 'testForUpdate1' + } + } + + def 'can create partLocale while calling update if partLocale does not exist'() { + given: + String partNumber = 'b8es' + queryParams.locale = 'es' + String json = '''{ + "specialInstructions": "testForUpdate" + }''' + path(partNumber) + ok() + + when: + put(json) + + then: + with(payload.results) { + partNumber + locale == 'es' + specialInstructionsLocale == 'testForUpdate' + } + } +} Index: src/integration-test/groovy/com/lemans/ds/product/ProductAssociationFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/product/ProductAssociationFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/product/ProductAssociationFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,214 @@ +package com.lemans.ds.product + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared + +class ProductAssociationFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'product' } + + private static final Integer PRODUCTID_1 = 316571 + + private static final Integer PRODUCTID_2 = 316572 + + private static final Integer RELATED_PRODUCTID_1 = 316573 + + private static final Integer RELATED_PRODUCTID_2 = 316574 + + private static final Integer RELATED_PRODUCTID_3 = 316571 + + private static final Integer ASSOCIATION_TYPE_ID_1 = 21001 + + private static final Integer ASSOCIATION_TYPE_ID_2 = 21002 + + @Shared + Integer productAssociationId1 + + @Shared + Integer productAssociationId2 + + @Shared + Integer productAssociationId3 + + def 'can not find related product'() { + given: + path([catalog: 0], '_invalid_/relatedProduct') + ok() + + when: + get() + + then: + payload.meta.totalRecords >= 1 + } + + def 'can not find referral product'() { + given: + path([catalog: 0], '_invalid_/referralProduct') + ok() + + when: + get() + + then: + payload.meta.totalRecords >= 1 + } + + def 'can not add productAssociation'() { + given: + Map inputPayload = [relatedProductId: RELATED_PRODUCTID_1] + path([catalog: 0], "$PRODUCTID_1/relatedProduct") + invalid() + + when: + post(inputPayload) + + then: + payload.messages[0].field == 'associationTypeId' + payload.messages[0].text == 'associationTypeId is required' + } + + def 'can add productAssociation example1'() { + given: + Map inputPayload = [associationTypeId: ASSOCIATION_TYPE_ID_1, relatedProductId: RELATED_PRODUCTID_1] + path([catalog: 0], "$PRODUCTID_1/relatedProduct") + ok() + + when: + post(inputPayload) + productAssociationId1 = payload.results.productAssociationId + + then: + payload.results.associationTypeId == ASSOCIATION_TYPE_ID_1 + payload.results.relatedProductId == RELATED_PRODUCTID_1 + } + + def 'can not add duplicate productAssociation example1'() { + given: + Map inputPayload = [associationTypeId: ASSOCIATION_TYPE_ID_1, relatedProductId: RELATED_PRODUCTID_1] + path([catalog: 0], "$PRODUCTID_1/relatedProduct") + invalid() + + when: + post(inputPayload) + + then: + payload.messages[0].type == 'error' + payload.messages[0].text == 'productAssociation for productId 316571 with associationTypeId ' + + ' 21001 and relatedProductId 316573 exists.' + } + + def 'can add productAssociation example2'() { + Map inputPayload = [associationTypeId: ASSOCIATION_TYPE_ID_1, relatedProductId: RELATED_PRODUCTID_2] + path([catalog: 0], "$PRODUCTID_1/relatedProduct") + ok() + + when: + post(inputPayload) + productAssociationId2 = payload.results.productAssociationId + + then: + payload.results.associationTypeId == ASSOCIATION_TYPE_ID_1 + payload.results.relatedProductId == RELATED_PRODUCTID_2 + } + + def 'can add productAssociation example3'() { + Map inputPayload = [associationTypeId: ASSOCIATION_TYPE_ID_1, relatedProductId: RELATED_PRODUCTID_3] + path([catalog: 0], "$PRODUCTID_2/relatedProduct") + ok() + + when: + post(inputPayload) + productAssociationId3 = payload.results.productAssociationId + + then: + payload.results.associationTypeId == ASSOCIATION_TYPE_ID_1 + payload.results.relatedProductId == RELATED_PRODUCTID_3 + } + + def 'can find related product'() { + given: + path([catalog: 0], "$PRODUCTID_1/relatedProduct") + ok() + + when: + get() + + then: + payload.results.productId.every { it == PRODUCTID_1 } + } + + def 'can find referral product'() { + given: + path([catalog: 0], "$PRODUCTID_1/referralProduct") + ok() + + when: + get() + + then: + payload.results.relatedProductId.every { it == PRODUCTID_1 } + } + + def 'can get productAssociation'() { + given: + path([catalog: 0], "$PRODUCTID_1/relatedProduct/$productAssociationId1") + ok() + + when: + get() + + then: + payload.results.associationTypeId == ASSOCIATION_TYPE_ID_1 + } + + def 'can update productAssociation'() { + given: + Map inputPayload = [associationTypeId: ASSOCIATION_TYPE_ID_2] + path([catalog: 0], "$PRODUCTID_1/relatedProduct/$productAssociationId1") + ok() + + when: + put(inputPayload) + + then: + payload.results.associationTypeId == ASSOCIATION_TYPE_ID_2 + } + + def 'can delete productAssociation example1'() { + given: + path([catalog: 0], "$PRODUCTID_1/relatedProduct/$productAssociationId1") + ok() + + when: + delete() + + then: + !payload + } + + def 'can delete productAssociation example2'() { + given: + path([catalog: 0], "$PRODUCTID_1/relatedProduct/$productAssociationId2") + ok() + + when: + delete() + + then: + !payload + } + + def 'can delete productAssociation example3'() { + given: + path([catalog: 0], "$PRODUCTID_2/relatedProduct/$productAssociationId3") + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/product/ProductFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/product/ProductFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/product/ProductFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,331 @@ +package com.lemans.ds.product + +import com.lemans.ds.CatalogTestFixture +import com.lemans.ds.testing.DsFuncSpec + +class ProductFuncSpec extends DsFuncSpec implements CatalogTestFixture { + + @Override + String resourceName() { 'product' } + + final static int ALL_COLUMNS_SIZE = 31 + + def 'can find a Product'() { + given: + int id = 316931 + path(catalog, id) + ok() + + when: + get() + + then: + payload.results.productId == id + payload.results.catalogInstanceId == catalogInstanceId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can search for a Product'() { + given: + int id = 316931 + queryParams.productId = id + path(catalog) + ok() + + when: + get() + + then: + payload.results[0].productId == id + payload.results[0].catalogInstanceId == catalogInstanceId + apiPaginated() == 1 + } + + def 'can find Products for a Brand by brandCode'() { + given: + String brand = '0821' + queryParams.brandCode = brand + queryParams.columns = 'productId,catalogInstanceId,brandCode,brandId' + path(catalog) + ok() + + when: + get() + + then: + payload.results.brandCode.every { it == brand } + payload.results.catalogInstanceId.every { it == catalogInstanceId } + apiPaginated() > 720 + } + + def 'can find Products belonging to single flagId'() { + given: + queryParams.isDigiActive = true + queryParams.flagId = 20 + queryParams.mode = 'flag' + queryParams.columns = 'productId,catalogInstanceId,brandCode,brandId' + path(catalog) + ok() + + when: + get() + + then: + payload.meta.totalRecords == 0 + } + + def 'can search products by flagId'() { + given: + queryParams.flagId = 17 + queryParams.columns = 'productId,catalogInstanceId,brandCode,brandId' + path(catalog) + ok() + + when: + get() + + then: + payload.results + } + + def 'can find Products belonging to a single flagId'() { + given: + queryParams.isDigiActive = true + queryParams.flagId = 19 + queryParams.brandCode = '0823' + queryParams.mode = 'flag' + queryParams.columns = 'productId,catalogInstanceId,brandCode,brandId' + path(catalog) + ok() + + when: + get() + + then: + payload.results[0].brandCode == '0823' + payload.meta.totalRecords >= 1 + } + + def 'can find Products for a Brand by brandId'() { + given: + String brand = 821 + queryParams.brandId = brand + queryParams.columns = 'productId,catalogInstanceId,brandCode,brandId' + path(catalog) + ok() + + when: + get() + + then: + payload.results.brandId.every { brand } + payload.results.catalogInstanceId.every { it == catalogInstanceId } + apiPaginated() > 720 + } + + def 'can find Products by isDigiActiveFilter with value 1'() { + given: + queryParams.isDigiActive = 1 + queryParams.columns = 'productId, catalogInstanceId, brandCode, productName' + path(catalog) + ok() + + when: + get() + + then: + payload.meta.totalRecords >= 10 + } + + def 'can find products by IsDigiActiveFilter with value 0'() { + given: + queryParams.isDigiActive = 0 + queryParams.columns = 'productId, catalogInstanceId, brandCode, productName' + path(catalog) + ok() + + when: + get() + + then: + payload.results[0].productId == 316590 + payload.meta.totalRecords >= 26770 + } + + def 'can find Products for a Category'() { + given: + Integer categoryId = 300 + queryParams.categoryId = categoryId + queryParams.columns = 'productId,catalogInstanceId,categoryId' + path(catalog) + ok() + + when: + get() + + then: + payload.results.categoryId.every { it == categoryId } + payload.results.catalogInstanceId.every { it == catalogInstanceId } + apiPaginated() >= 2 + } + + def 'can find Products'() { + given: + queryParams.columns = 'productId, catalogInstanceId, productName' + path(catalog) + ok() + + when: + get() + + then: + payload.results.productId.every { it != null } + payload.results.catalogInstanceId.every { it == catalogInstanceId } + apiPaginated() > 13000 + } + + def 'can find Products Part Descriptions'() { + given: + queryParams.columns = 'productId, catalogInstanceId, productName' + path(catalog) + ok() + + when: + get() + + then: + payload.results.productId.every { it != null } + payload.results.catalogInstanceId.every { it == catalogInstanceId } + apiPaginated() > 13000 + } + + def 'can find Products like a specific productName'() { + given: + String name = 'testing' + queryParams.productName = name + '*' + queryParams.columns = 'productId,catalogInstanceId,productName' + path(catalog) + ok() + + when: + get() + + then: + payload.results.productId.every { it != null } + payload.results.catalogInstanceId.every { it == catalogInstanceId } + payload.results.productName.every { it.startsWith(name) } + apiPaginated() > 9 + } + + def 'can find Products with generic q param search on productId '() { + given: + String partialProductId = '32' + queryParams.q = partialProductId + queryParams.columns = 'productId,catalogInstanceId,productName' + path(catalog) + ok() + + when: + get() + + then: + payload.results.productId.every { it.toString().contains(partialProductId) } + payload.results.catalogInstanceId.every { it == catalogInstanceId } + apiPaginated() > 1000 + } + + def 'can find Products with generic q param search on productName '() { + given: + String partialProductName = 'LOOKY' + queryParams.q = partialProductName + queryParams.columns = 'productId,catalogInstanceId,productName' + path(catalog) + ok() + + when: + get() + + then: + payload.results.productName.every { it.toString().contains(partialProductName) } + payload.results.catalogInstanceId.every { it == catalogInstanceId } + apiPaginated() > 0 + } + + def 'can create a product'() { + given: + String json =''' + { + "productName": "dfd", + "categoryId": "4537", + "brandId": "0649" + } + ''' + path(catalog) + ok() + + when: + post(json) + + then: + payload + } + + def 'can find products without flag param'() { + given: + String partialProductId = '32' + queryParams.q = partialProductId + queryParams.columns = 'productId,catalogInstanceId,productName' + path(catalog) + ok() + + when: + get() + + then: + payload + } + + def 'can find products with flag param'() { + given: + String partialProductId = '32' + queryParams.q = partialProductId + queryParams.mode = 'flag' + queryParams.columns = 'productId,catalogInstanceId,productName' + path(catalog) + ok() + + when: + get() + + then: + payload + } + + def 'can get split validation'() { + given: + Map data = [bulkValidation: 0] + path([catalog: 0], '316651/splitValidation') + ok() + + when: + post(data) + + then: + payload + } + + def 'can get products with multiple categoryIds'() { + given: + queryParams.categoryId = [4542, 4543] + queryParams.isDigiActive = 1 + queryParams.columns = 'productId,catalogInstanceId,productName' + path(catalog) + ok() + + when: + get() + + then: + payload + } +} Index: src/integration-test/groovy/com/lemans/ds/product/ProductLocaleFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/product/ProductLocaleFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/product/ProductLocaleFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,332 @@ +package com.lemans.ds.product + +import com.lemans.ds.CatalogTestFixture +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Stepwise + +import java.text.SimpleDateFormat + +@Stepwise +class ProductLocaleFuncSpec extends DsFuncSpec implements CatalogTestFixture { + + @Override + String resourceName() { 'product' } + + final static int ALL_COLUMNS_SIZE = 30 + String effective = '2016-11-18T08:12:33' + SimpleDateFormat dateTimeFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.default) + Date effectiveDate = dateTimeFormatter.parse(effective) + + Integer validProductId = 346004 + + Integer invalidProductId = 34614 + + + + def 'can create ProductLocale with productId that exist'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'fr' + path(catalog, validProductId) + ok() + + when: + put([productName: name]) + + then: + payload.results.productId == validProductId + payload.results.productNameLocale == name + } + + def 'can add ProductLocale with productId that exist'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'fr' + path(catalog, validProductId) + ok() + + when: + put([productName: name]) + + then: + payload.results.productId == validProductId + payload.results.productNameLocale == name + } + + def 'can NOT create ProductLocale with productId that exist but with invalid Locale'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'xy' + path(catalog, validProductId) + invalid() + + when: + put([productName: name]) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'locale' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + + def 'can NOT create ProductLocale with productId that does not exist'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'de' + path(catalog, invalidProductId) + notFound() + + when: + put([productName: name]) + + then: + !payload + } + + def 'can NOT create ProductLocale with invalid Locale'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'xy' + path(catalog, validProductId) + invalid() + + when: + put([productName: name]) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'locale' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + def 'can NOT update ProductLocale with invalid Locale'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'xy' + path(catalog, validProductId) + invalid() + + when: + put([productName: name]) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'locale' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + def 'can NOT update ProductLocale with invalid ProductId'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'de' + path(catalog, invalidProductId) + notFound() + + when: + put([productName: name]) + + then: + !payload + } + + def 'can update ProductLocale with valid Locale'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'de' + path(catalog, validProductId) + ok() + + when: + put([productName: name]) + + then: + payload.results.productNameLocale == name + payload.results.productId == validProductId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can update empty ProductNameLocale with valid Locale'() { + given: + String name = '' + optionalHeaders.locale = 'de' + path(catalog, validProductId) + ok() + + when: + put([productName: name]) + + then: + payload.results.productNameLocale == null + payload.results.productId == validProductId + payload.results.size() == ALL_COLUMNS_SIZE + } + + + def 'can NOT get all ProductLocale for invalid Locale'() { + given: + queryParams.columns = 'productId, productNameLocale' + optionalHeaders.locale = '_xy' + path(catalog) + ok() + + when: + get() + + then: + payload.results == [] + payload.meta.totalRecords == 0 + } + + def 'can get all ProductLocale for a valid Locale'() { + given: + queryParams.columns = 'productId, productNameLocale' + optionalHeaders.locale = 'de' + path(catalog) + ok() + + when: + get() + + then: + /*payload.results.productId.every { it == catalogInstanceId } + payload.results.productLocaleId.every { it == productLocaleId }*/ + apiPaginated() >= 2 + } + + def 'can NOT find ProductLocale for an invalid ProductId for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path(catalog, invalidProductId) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find ProductLocale for a valid ProductId for a invalid Locale'() { + given: + optionalHeaders.locale = 'xy' + path(catalog, validProductId) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find ProductLocale for invalid ProductId for invalid Locale'() { + given: + optionalHeaders.locale = 'xy' + path(catalog, invalidProductId) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find ProductLocale for a valid ProductId for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path(catalog, validProductId) + ok() + + when: + get() + + then: + payload.results.productId == validProductId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can get ProductLocale for a valid ProductId for a valid Locale'() { + given: + optionalHeaders.locale = 'fr' + path(catalog, validProductId) + ok() + + when: + get() + + then: + payload.results.productId == validProductId + payload.results.size() == ALL_COLUMNS_SIZE + } + + + + + def 'can find Products like a specific productName for a valid Locale'() { + given: + String name = 'testing' + queryParams.productNameLocale = name + '*' + queryParams.columns = 'productId,catalogInstanceId,productNameLocale' + optionalHeaders.locale = 'de' + path(catalog) + ok() + + when: + get() + + then: + payload.results.productId.every { it != null } + payload.results.catalogInstanceId.every { it == catalogInstanceId } + payload.results.productNameLocale.every { it.startsWith(name) } + //apiPaginated() > 9 + } + + def 'can find Products with generic q param search on productId '() { + given: + String partialProductId = '32' + queryParams.q = partialProductId + queryParams.columns = 'productId,catalogInstanceId,productNameLocale' + optionalHeaders.locale = 'fr' + path(catalog) + ok() + + when: + get() + + then: + payload.results.productId.every { it.toString().contains(partialProductId) } + payload.results.catalogInstanceId.every { it == catalogInstanceId } + apiPaginated() > 1000 + } + + def 'can find Products with generic q param search on productNameLocale for a valid Locale '() { + given: + String partialProductName = 'Locale' + queryParams.q = partialProductName + optionalHeaders.locale = 'fr' + queryParams.columns = 'productId,catalogInstanceId,productNameLocale' + path(catalog) + ok() + + when: + get() + + then: + payload.results.productNameLocale.every { it.toString().contains(partialProductName) } + payload.results.catalogInstanceId.every { it == catalogInstanceId } + apiPaginated() > 0 + } + + +} Index: src/integration-test/groovy/com/lemans/ds/product/ProductPartFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/product/ProductPartFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/product/ProductPartFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,243 @@ +package com.lemans.ds.product + +import com.lemans.ds.CatalogTestFixture +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Stepwise + +@Stepwise +class ProductPartFuncSpec extends DsFuncSpec implements CatalogTestFixture { + + final static int ALL_COLUMNS_SIZE = 19 + + final static int PARTS_COLUMNS_SIZE = 3 + + Integer productId = 316931 + String partNumber1 = '01050001' + String partNumber2 = '01050003' + + @Override + String resourceName() { 'part' } + + def 'cleanUpData'() { + given: + String json = """ +{ +"operation": "DELETE", +"partNumbers": ["$partNumber1", "$partNumber2"] +} +""" + path(catalog + [product: productId]) + ok() + + when: + post(json) + + then: + !payload.results + } + + def 'can NOT perform bulk operation on Product Part relation with no partNumber'() { + given: + String json = ''' +{ +"operation": "INSERT" +} +''' + path(catalog + [product: productId]) + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + text.contains('No Part Selected') + } + } + + def 'can NOT perform bulk operation on Product Part relation with empty partNumbers'() { + given: + String json = ''' +{ +"operation": "INSERT", +"partNumbers": [] +} +''' + path(catalog + [product: productId]) + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + text.contains('No Part Selected') + } + } + + def 'can NOT perform bulk operation on Product Part relation with invalid productId'() { + given: + String json = """ +{ +"operation": "INSERT", +"partNumber": "$partNumber1" +} +""" + path(catalog + [product: -1]) + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + text.contains('Invalid product') + } + } + + def 'can NOT perform bulk operation on Product Part with brand and catalog mismatch of part and product'() { + given: + String json = ''' +{ +"operation": "INSERT", +"partNumber": "B8ES" +} +''' + path(catalog + [product: productId]) + invalid() + + when: + post(json) + + then: + with(payload.messages[0]) { + type == 'error' + text.contains('doesn\'t match') + } + } + + def 'can ADD single Product Part relation '() { + given: + String json = ''' +{ +"operation": "INSERT", +"partNumber": "b8es" +} +''' + path(catalog + [product: 316651]) + ok() + + when: + post(json) + + then: + !payload.results + } + + def 'can perform bulk ADD operation on Product Part relation'() { + given: + String json = ''' +{ +"operation": "INSERT", +"partNumbers": ["b8es"] +} +''' + path(catalog + [product: 316651]) + ok() + + when: + post(json) + + then: + !payload.results + } + + def 'can NOT find Product Part relation with invalid partNumber'() { + given: + path(catalog + [product: productId], -1) + notFound() + + when: + get() + + then: + !payload + + } + + def 'can find Product Part relation by product and Part'() { + given: + path(catalog + [product: 316931], '0010513') + ok() + + when: + get() + + then: + payload.results.productId == 316931 + payload.results.partNumber == '0010513' + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can find ALL Product Part relation by product'() { + given: + path(catalog + [product: productId]) + ok() + + when: + get() + + then: + payload.results.size() >= 2 + payload.results.every { it.productId == productId } + payload.results[0].size() == ALL_COLUMNS_SIZE + } + def 'can perform single DELETE on Product Part relation'() { + given: + + path(catalog + [product: productId], partNumber1) + ok() + + when: + delete() + + then: + !payload.results + } + + def 'can perform bulk DELETE on Product Part relation'() { + given: + String json = """ +{ +"operation": "DELETE", +"partNumbers": ["$partNumber2"] +} +""" + path(catalog + [product: productId]) + ok() + + when: + post(json) + + then: + !payload.results + } + + def 'can find ALL Product Part Merchandising details by product'() { + given: + queryParams.attribute = true + path(catalog + [product: productId]) + ok() + + when: + get() + + then: + payload.results.size() >= 1 + payload.results.productId == productId + payload.results.part[0].attribute[0].size() == PARTS_COLUMNS_SIZE + } +} Index: src/integration-test/groovy/com/lemans/ds/product/ProductPersistenceFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/product/ProductPersistenceFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/product/ProductPersistenceFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,125 @@ +package com.lemans.ds.product + +import com.lemans.ds.CatalogTestFixture +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared +import spock.lang.Stepwise + +import java.text.SimpleDateFormat + +@Stepwise +class ProductPersistenceFuncSpec extends DsFuncSpec implements CatalogTestFixture { + + @Override + String resourceName() { 'product' } + + @Shared Integer productId + + String effective = '2016-11-18T08:12:33' + SimpleDateFormat dateTimeFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.default) + Date effectiveDate = dateTimeFormatter.parse(effective) + + def 'can NOT create an invalid Product'() { + given: + path(catalog: null) + invalid() + + when: + post() + + then: + payload.messages.find { it.text == 'Catalog is required' } + } + + def 'can NOT update the Catalog for a Product'() { + given: + path(catalog, 343170) + invalid() + + when: + put([catalogInstanceId: 1]) + List errors = payload.messages.findAll { it.type = 'error' } + + then: + errors.find { it.field == 'catalogInstanceId' }.text == 'Catalog may not be changed' + } + + def 'can NOT update an invalid Product'() { + given: + path(catalog, 343170) + invalid() + + when: + put([productName: null, primaryMediaId: -1]) + List errors = payload.messages.findAll { it.type = 'error' } + + then: + errors.find { it.field == 'productName' } + errors.find { it.field == 'primaryMediaId' }.text == 'PrimaryMediaId with value [-1] does not exist' + } + + def 'can NOT delete a Product that does not exist'() { + given: + path(catalog: -1, 343170) + notFound() + + when: + delete() + + then: + !payload + } + + def 'can create a valid Product for the VirtualCatalog'() { + given: + Integer brand = 839 + Integer category = 81 + String name = 'testing_123' + Map product = [brandId: brand, categoryId: category, productName: name, effectiveDate: effective, isDigiActive: '1'] + path(catalog) + ok() + + when: + post(product) + productId = payload.results.productId + + then: + with(payload.results) { + brandId == brand + categoryId == category + productName == name + catalogInstanceId == this.catalogInstanceId + effectiveDate == this.effective + } + } + + def 'can update a valid Product'() { + given: + String name = 'more_testing' + Integer mediaId = 228030 + path(catalog, productId) + ok() + + when: + put([productName: name, effectiveDate: effective, primaryMediaId: mediaId]) + + then: + payload.results.productId == productId + payload.results.productName == name + payload.results.primaryMediaId == mediaId + payload.results.effectiveDate == effective + } + + + def 'can delete a Product'() { + given: + path(catalog, productId) + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,36 @@ +package com.lemans.ds.product.feature + +import com.lemans.ds.testing.DsFuncSpec + +class ProductFeatureFuncSpec extends DsFuncSpec implements ProductFeatureTestFixture { + + @Override + String resourceName() { 'feature' } + + def 'can find a Feature for a Product'() { + given: + Integer featureId = 54 + path(catalogProduct, featureId) + ok() + + when: + get() + + then: + payload.results.productFeatureId == featureId + payload.results.productId == productId + } + + def 'can find Features for a Product'() { + given: + path(catalogProduct) + ok() + + when: + get() + + then: + payload.results.productId.every { it == productId } + payload.results.productFeatureId.every { it } + } +} Index: src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureLocaleFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureLocaleFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureLocaleFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,234 @@ +package com.lemans.ds.product.feature + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Stepwise + +@Stepwise +class ProductFeatureLocaleFuncSpec extends DsFuncSpec implements ProductFeatureTestFixture { + + @Override + String resourceName() { 'feature' } + + Integer validProductFeatureId = 1788329 + + Integer invalidProductFeatureId = 11000000 + + final static int ALL_COLUMNS_SIZE = 19 + + + + def 'can create ProductFeatureLocale with productFeatureId that exist'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'de' + path(catalogProduct, validProductFeatureId) + ok() + + when: + put([featureText: name]) + + then: + payload.results.productFeatureId == validProductFeatureId + //payload.results.featureTextLocale == name + } + + def 'can NOT create ProductFeatureLocale with productFeatureId that exist but with invalid Locale'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'xy' + path(catalogProduct, validProductFeatureId) + invalid() + + when: + put([featureText: name]) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'locale' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + + def 'can NOT create ProductFeatureLocale with productFeatureId that does not exist'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'de' + path(catalogProduct, invalidProductFeatureId) + notFound() + + when: + put([featureText: name]) + + then: + !payload + } + + def 'can NOT create ProductFeatureLocale with invalid Locale'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'xy' + path(catalogProduct, validProductFeatureId) + invalid() + + when: + put([featureText: name]) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'locale' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + def 'can NOT update ProductFeatureLocale with invalid Locale'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'xy' + path(catalogProduct, validProductFeatureId) + invalid() + + when: + put([featureText: name]) + + then: + with(payload.messages[0]) { + type == 'error' + field == 'locale' + text == 'locale with value [xy] is not contained within the list [[de, it, es, fr]]' + } + payload.messages.size() == 1 + } + + def 'can NOT update ProductFeatureLocale with invalid productFeatureId'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'de' + path(catalogProduct, invalidProductFeatureId) + notFound() + + when: + put([featureText: name]) + + then: + !payload + } + + def 'can update ProductFeatureLocale with valid Locale'() { + given: + String name = 'German_more_testing' + optionalHeaders.locale = 'de' + path(catalogProduct, validProductFeatureId) + ok() + + when: + put([featureText: name]) + + then: + payload.results.featureTextLocale == name + payload.results.productFeatureId == validProductFeatureId + payload.results.size() == ALL_COLUMNS_SIZE + } + + def 'can update with empty ProductFeatureLocale with valid Locale'() { + given: + String name = '' + optionalHeaders.locale = 'de' + path(catalogProduct, validProductFeatureId) + ok() + + when: + put([featureText: name]) + + then: + payload.results.featureTextLocale == null + payload.results.productFeatureId == validProductFeatureId + payload.results.size() == ALL_COLUMNS_SIZE + } + + + def 'can NOT get all ProductFeatureLocale for invalid Locale'() { + given: + optionalHeaders.locale = 'xy' + path(catalogProduct) + ok() + + when: + get() + + then: + payload.results == [] + payload.meta.totalRecords == 0 + } + + def 'can get all ProductFeatureLocale for a product for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path(catalogProduct) + ok() + + when: + get() + + then: + payload.results.productId.every { it == productId } + payload.results.productFeatureId.every { it } + } + + def 'can NOT find ProductFeatureLocale for an invalid ProductFeatureId for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path(catalogProduct, invalidProductFeatureId) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find ProductFeatureLocale for a valid ProductFeatureId for a invalid Locale'() { + given: + optionalHeaders.locale = 'xy' + path(catalogProduct, validProductFeatureId) + notFound() + + when: + get() + + then: + !payload + } + + def 'can NOT find ProductFeatureLocale for invalid ProductFeatureId for invalid Locale'() { + given: + optionalHeaders.locale = 'xy' + path(catalogProduct, invalidProductFeatureId) + notFound() + + when: + get() + + then: + !payload + } + + def 'can find ProductFeatureLocale for a valid ProductFeatureId for a valid Locale'() { + given: + optionalHeaders.locale = 'de' + path(catalogProduct, validProductFeatureId) + ok() + + when: + get() + + then: + payload.results.productFeatureId == validProductFeatureId + payload.results.size() == ALL_COLUMNS_SIZE + } +} Index: src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureMoveFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureMoveFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureMoveFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,141 @@ +package com.lemans.ds.product.feature + +import com.lemans.ds.MoveCommand +import com.lemans.ds.product.Product +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared +import spock.lang.Stepwise + +@Stepwise +class ProductFeatureMoveFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'feature' } + + @Shared List features + @Shared Map feature1 + @Shared Map feature2 + @Shared Map feature3 + @Shared Map feature4 + @Shared Map feature5 + @Shared Map feature6 + + List initialFeatureOrder = [1778579, 1778580, 1778581, 1778582, 1778583, 1778584] + + Integer productId = 342390 + Map catalogProduct = [catalog: Product.VIRTUAL_CATALOG_ID, product: productId] + + // TODO: add MoveCommand unit tests + + // TODO: make sure move fails on different products +// def 'can NOT move a ProductFeature to a different Product'() { +// +// } + + def 'load initial Features for a Product'() { + given: + queryParams.pageSize = 10 + path(catalogProduct) + ok() + + when: + get() + populateFeatures(payload.results) + + then: + assertFeaturesAreOrderedBySequence() + assertFeatureOrder(initialFeatureOrder) + } + + def 'can move a ProductFeature after another'() { + given: + Map move = [targetId: 1778581, position: MoveCommand.AFTER] + path(catalogProduct, '1778579/move') + ok() + + when: + put(move) + + then: + payload.results.productFeatureId == 1778579 + payload.results.sequence == 3 + } + + def 'load Features for a Product after MOVE AFTER'() { + given: + queryParams.pageSize = 10 + path(catalogProduct) + ok() + + when: + get() + populateFeatures(payload.results) + + then: + assertFeaturesAreOrderedBySequence() + assertFeatureOrder([1778580, 1778581, 1778579, 1778582, 1778583, 1778584]) + } + + def 'can move a ProductFeature BEFORE another'() { + given: + Map move = [targetId: 1778580, position: MoveCommand.BEFORE] + path(catalogProduct, '1778579/move') + ok() + + when: + put(move) + + then: + payload.results.productFeatureId == 1778579 + } + + def 'load Features for a Product after MOVE BEFORE'() { + given: + queryParams.pageSize = 10 + path(catalogProduct) + ok() + + when: + get() + populateFeatures(payload.results) + + then: + assertFeaturesAreOrderedBySequence() + assertFeatureOrder(initialFeatureOrder) + } + + private void assertFeatureOrder(List ids) { + with(this) { + feature1.productFeatureId == ids[0] + feature2.productFeatureId == ids[1] + feature3.productFeatureId == ids[2] + feature4.productFeatureId == ids[3] + feature5.productFeatureId == ids[4] + feature6.productFeatureId == ids[5] + } + true + } + + private boolean assertFeaturesAreOrderedBySequence() { + with(this) { + features.size() == 6 + feature1.sequence == 1 + feature2.sequence == 2 + feature3.sequence == 3 + feature4.sequence == 4 + feature5.sequence == 5 + feature6.sequence == 6 + } + true + } + + private void populateFeatures(featureList) { + features = featureList + feature1 = features[0] + feature2 = features[1] + feature3 = features[2] + feature4 = features[3] + feature5 = features[4] + feature6 = features[5] + } +} Index: src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeaturePersistenceFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeaturePersistenceFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeaturePersistenceFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,75 @@ +package com.lemans.ds.product.feature + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared +import spock.lang.Stepwise + +@Stepwise +class ProductFeaturePersistenceFuncSpec extends DsFuncSpec implements ProductFeatureTestFixture { + + @Override + String resourceName() { 'feature' } + + @Shared Integer productFeatureId + + def 'can create multiple Product Features'() { + given: + path(catalogProduct) + ok() + + when: + post(validFeatures) + List errors = errors() + + then: + !errors + payload.messages.find { it.type == 'info' }.text == 'Created 2 Product Feature(s)' + payload.results.size() == 2 + } + + def 'can create a Product Feature'() { + given: + path(catalogProduct) + ok() + + when: + post(validFeature1) + List errors = errors() + productFeatureId = payload.results[0].productFeatureId + + then: + !errors + payload.messages.find { it.type == 'info' }.text == 'Created 1 Product Feature(s)' + payload.results.size() == 1 + } + + def 'can update a Product Feature'() { + given: + String text = 'wtf_' + new Date() + path(catalogProduct, productFeatureId) + ok() + + when: + put([featureText: text]) + + then: + payload.results.productFeatureId == productFeatureId + payload.results.featureText == text + } + + def 'can delete a Product Feature'() { + given: + path(catalogProduct, productFeatureId) + ok() + + when: + delete() + + then: + !payload + } + + private List errors() { + payload.messages.findAll { it.type == 'error' } + } +} Index: src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureTestFixture.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureTestFixture.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureTestFixture.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,19 @@ +package com.lemans.ds.product.feature + +import com.lemans.ds.product.Product + + +trait ProductFeatureTestFixture { + + Integer catalogInstanceId = Product.VIRTUAL_CATALOG_ID + Integer productId = 333467 + Map catalogProduct = [catalog: catalogInstanceId, product: productId] + + Integer featureTypeId = 13001 + Map invalidFeature1 = [featureText: 'abc', featureTypeId: null] + Map invalidFeature2 = [featureText: null, featureTypeId: featureTypeId] + Map validFeature1 = [featureText: 'abc', featureTypeId: featureTypeId] + Map validFeature2 = [featureText: 'xyz', featureTypeId: featureTypeId] + Map invalidFeatures = [features: [invalidFeature1, validFeature1, invalidFeature2, validFeature2]] + Map validFeatures = [features: [validFeature1, validFeature2]] +} Index: src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureValidationFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureValidationFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/product/feature/ProductFeatureValidationFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,139 @@ +package com.lemans.ds.product.feature + +import com.lemans.ds.testing.DsFuncSpec + +class ProductFeatureValidationFuncSpec extends DsFuncSpec implements ProductFeatureTestFixture { + + @Override + String resourceName() { 'feature' } + + def 'can NOT create a ProductFeature without a Catalog'() { + given: + path(catalog: null, product: productId) + invalid() + + when: + post(validFeature1) + List errors = errors() + + then: + errors[0].text == 'Catalog is required' + errors.size() == 1 + } + + def 'can NOT create a ProductFeature without a Product'() { + given: + path(catalog: catalogInstanceId, product: null) + invalid() + + when: + post(validFeature1) + List errors = errors() + + + then: + errors[0].text == 'productId is required' + errors.size() == 1 + } + + def 'can NOT create a ProductFeature with an invalid Product'() { + given: + path(catalog: catalogInstanceId, product: -1) + invalid() + + when: + post(validFeature1) + List errors = errors() + + then: + errors[0].text == 'Invalid productId' + errors.size() == 1 + } + + def 'can NOT create a ProductFeature with an invalid Catalog'() { + given: + path(catalog: -1, product: productId) + invalid() + + when: + post(validFeature1) + List errors = errors() + + then: + errors.size() == 1 + errors[0].text == 'Invalid catalog instance [-1] for the product' + } + + def 'can NOT create an invalid ProductFeature'() { + given: + path(catalogProduct) + invalid() + + when: + post(invalidFeature1) + List errors = errors() + + then: + errors.size() == 1 + errors[0].text == 'Product Feature Type is required' + errors[0].index == 1 + } + + def 'can NOT create invalid ProductFeatures'() { + given: + path(catalogProduct) + invalid() + + when: + post(invalidFeatures) + List errors = errors() + + then: + errors.size() == 2 + errors[0].text == 'Product Feature Type is required' + errors[0].index == 1 + errors[1].text == 'Product Feature Text is required' + errors[1].index == 3 + } + + def 'can NOT update an invalid ProductFeature'() { + given: + path(catalogProduct, 54) + invalid() + + when: + put([featureText: null, featureTypeId: null]) + + then: + payload.messages.find { it.text == 'Product Feature Text is required' } + payload.messages.find { it.text == 'Product Feature Type is required' } + } + + def 'can NOT update a ProductFeature that does not exist'() { + given: + path(catalogProduct, -1) + notFound() + + when: + put() + + then: + !payload + } + + def 'can NOT delete a ProductFeature that does not exist'() { + given: + path(catalogProduct, -1) + notFound() + + when: + delete() + + then: + !payload + } + + private List errors() { + payload.messages.findAll { it.type == 'error' } + } +} Index: src/integration-test/groovy/com/lemans/ds/product/merchandising/ProductMerchandisingFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/product/merchandising/ProductMerchandisingFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/product/merchandising/ProductMerchandisingFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,92 @@ +package com.lemans.ds.product.merchandising + +import com.lemans.ds.CatalogTestFixture +import com.lemans.ds.testing.DsFuncSpec + +/** + * Created by MUmachi on 10/16/2017. + */ +class ProductMerchandisingFuncSpec extends DsFuncSpec implements CatalogTestFixture { + + @Override + String resourceName() { 'attribute' } + + final static int ALL_COLUMNS_SIZE = 10 + + Integer productId = 343229 + Integer categoryAttributeId = 32177 + + def 'can find ALL Product Merchandising details by product'() { + given: + path(catalog + [product: productId]) + ok() + + when: + get() + + then: + payload.results.size() >= 1 + //payload.results.productId == productId + //payload.results.attribute[0].size() >= ALL_COLUMNS_SIZE + //payload.results.attribute.mediaUrl + //payload.results.attribute.partDescr + //payload.results.attribute.partNumber + //payload.results.attribute.punctuatedPartNumber + } + + def 'can create or update isDropdown Attribute by productId and CategoryAttributeId'() { + given: + path(catalog + [product: 316931], 45891) + + Boolean isDropdown = false + + String json = """ +{ +"isDropdown": ${isDropdown}, + }""" + ok() + + when: + put(json) + + then: + //payload.results.isDropdown == isDropdown + payload.results.size() == 15 + } + + def 'can create or update isHidden Attribute by productId and CategoryAttributeId'() { + given: + path(catalog + [product: productId], categoryAttributeId) + Boolean isHidden = true + String json = """ +{ +"isHidden": $isHidden, + }""" + ok() + + when: + put(json) + + then: + payload.results.isHidden == isHidden + payload.results.size() == 15 + } + + def 'can create or update isSplit Attribute by productId and CategoryAttributeId'() { + given: + path(catalog + [product: productId], categoryAttributeId) + Boolean isSplit = true + String json = """ +{ +"isSplit": $isSplit, + }""" + ok() + + when: + put(json) + + then: + payload.results.isSplit == isSplit + payload.results.size() == 15 + } +} Index: src/integration-test/groovy/com/lemans/ds/publicationcategory/ProductPublicationCategoryFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/publicationcategory/ProductPublicationCategoryFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/publicationcategory/ProductPublicationCategoryFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,65 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Stepwise + +@Stepwise +class ProductPublicationCategoryFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'publicationCategory' } + + def 'can create ProductPublicationCategory'() { + Map json = [productId: 316782] + Integer categoryId = 15 + path("$categoryId/product") + ok() + + when: + post(json) + + then: + payload.results.categoryId == 15 + payload.results.productId == 316782 + } + + def 'can get publicationCategory'() { + given: + Integer categoryId = 15 + path("$categoryId/product") + ok() + + when: + get() + + then: + payload.results.every { categoryId == 15 } + } + + def 'can get categories'() { + given: + Integer productId = 316782 + path([product: productId]) + ok() + + when: + get() + + then: + payload.results.every { productId } + } + + def 'can delete productPublicationCategory'() { + given: + Integer categoryId = 15 + Integer productId = 316782 + path("$categoryId/product/$productId") + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryAttributeFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryAttributeFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryAttributeFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,142 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.ds.testing.DsFuncSpec + +class PublicationCategoryAttributeFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'publicationCategory' } + + Integer publicationCategoryId = 15 + + Integer invalidPublicationCategoryId = -1 + + Integer attributeNameId = 1 + + Integer invalidAttributeNameId = -1 + + def 'can not create a publicationCategoryAttribute'() { + given: + Map input = [:] + path("$publicationCategoryId/attribute") + invalid() + + when: + post(input) + + then: + payload.messages[0].field == 'attributeNameId' + payload.messages[0].text == 'attributeNameId is required' + } + + def 'can not create with invalid publication category'() { + given: + Map input = [:] + path("$invalidPublicationCategoryId/attribute") + invalid() + + when: + post(input) + + then: + payload.messages[0].field == 'attributeNameId' + payload.messages[0].text == 'attributeNameId is required' + } + + def 'can not find attributes for invalidPublicationCategory'() { + given: + path("$invalidPublicationCategoryId/attribute") + ok() + + when: + get() + + then: + payload + } + + def 'can not find invalid publicationCategoryAttribute'() { + given: + path("$invalidPublicationCategoryId/attribute/$invalidAttributeNameId") + notFound() + + when: + get() + + then: + !payload + } + + def 'can create a publicationCategoryAttribute'() { + given: + Map input = [attributeNameId: 1] + path("$publicationCategoryId/attribute") + ok() + + when: + post(input) + + then: + payload + } + + def 'can not create publication with same attributeNameId and publicationCategroyId'() { + given: + Map input = [attributeNameId: 1] + path("$publicationCategoryId/attribute") + invalid() + + when: + post(input) + + then: + payload.messages[0].type == 'error' + payload.messages[0].text == 'Duplicate publicationCategoryAttribute' + } + + def 'can find all publicationCategoryAttributes'() { + given: + path("$publicationCategoryId/attribute") + ok() + + when: + get() + + then: + payload + } + + def 'can find a publicationCategoryAttribute'() { + given: + path("$publicationCategoryId/attribute/$attributeNameId") + ok() + + when: + get() + + then: + payload + } + + def 'can delete a publicationCategoryAttribute'() { + path("$publicationCategoryId/attribute/$attributeNameId") + ok() + + when: + delete() + + then: + !payload + } + + def 'can get possible attribute options for a publicationCategory'() { + path('369/attributeOptions') + ok() + + when: + get() + + then: + payload + } +} Index: src/integration-test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryAttributeMoveFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryAttributeMoveFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryAttributeMoveFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,99 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared +import spock.lang.Stepwise + +@Stepwise +class PublicationCategoryAttributeMoveFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'publicationCategory' } + + Integer publicationCategoryId = 15 + + Integer sourceAttributeNameId = 9 + + Integer targetAttributeNameId = 1 + + @Shared Integer target + + @Shared Integer source + + def 'can create one publicationCategoryAttribute'() { + given: + Map input = [attributeNameId: 1] + path("$publicationCategoryId/attribute") + ok() + + when: + post(input) + target = payload.results.publicationCategoryAttributeId + + + then: + payload + } + + def 'can create two publicationCategoryAttribute'() { + given: + Map input = [attributeNameId: 9] + path("$publicationCategoryId/attribute") + ok() + + when: + post(input) + source = payload.results.publicationCategoryAttributeId + + then: + payload + } + + def 'can move before'() { + given: + Map input = [targetId: targetAttributeNameId, position: 'BEFORE'] + path("$publicationCategoryId/attribute/$sourceAttributeNameId/move") + ok() + + when: + put(input) + + then: + payload + } + + def 'can move after'() { + given: + Map input = [targetId: targetAttributeNameId, position: 'AFTER'] + path("$publicationCategoryId/attribute/$sourceAttributeNameId/move") + ok() + + when: + put(input) + + then: + payload + } + + def 'can delete publicationCategoryAttribute1'() { + path("$publicationCategoryId/attribute/$sourceAttributeNameId") + ok() + + when: + delete() + + then: + !payload + } + + def 'can delete publicationCategoryAttribute2'() { + path("$publicationCategoryId/attribute/$targetAttributeNameId") + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,130 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared + +class PublicationCategoryFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'publicationCategory' } + + @Shared + Integer parent + + @Shared + Integer child + + def 'can not create a parent publicationCategory'() { + given: + Map json = [description: 'Third'] + path() + invalid() + + when: + post(json) + + then: + payload.messages[0].text == 'categoryName is required' + } + + def 'can create a parent publicationCategory'() { + given: + Map json = [description: 'Third', categoryName: 'Third Category'] + path() + ok() + + when: + post(json) + parent = payload.results.categoryId + + then: + payload + } + + def 'can create a child publicationCategory'() { + given: + Map json = [description: 'child', categoryName: 'Child of Third Category', parentCategoryId: parent] + path() + ok() + + when: + post(json) + child = payload.results.categoryId + + then: + payload + } + + def 'can get publicationCAtegories'() { + given: + path() + ok() + + when: + get() + + then: + payload + } + + def 'can get publicationCategory'() { + given: + path("$parent") + ok() + + when: + get() + + then: + payload + } + + def 'can update publicationCAtegory'() { + given: + Map json = [description: 'updated', categoryName: 'Third Category'] + path("$parent") + ok() + + when: + put(json) + + then: + payload + } + + def 'can not delete a parent publicationCAtegory'() { + given: + path("$parent") + invalid() + + when: + delete() + + then: + payload.messages[0].text == 'Publication Category may not be deleted' + } + + def 'can delete a child publicationCAtegory'() { + given: + path("$child") + ok() + + when: + delete() + + then: + !payload + } + + def 'can delete parent publication category'() { + given: + path("$parent") + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryMoveFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryMoveFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryMoveFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,201 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.ds.testing.DsFuncSpec +import spock.lang.Shared + +class PublicationCategoryMoveFuncSpec extends DsFuncSpec { + + @Shared + Integer parent + + @Shared + Integer parent2 + + @Shared + Integer child1 + + @Shared + Integer child2 + + def 'can create a parent publicationCategory'() { + given: + Map json = [description: 'second Parent Category', categoryName: 'category2'] + path('publicationCategory') + ok() + + when: + post(json) + parent = payload.results.categoryId + + then: + payload + } + + def 'can create second parent publicationCategory'() { + given: + Map json = [description: 'second Parent Category', categoryName: 'category2'] + path('publicationCategory') + ok() + + when: + post(json) + parent2 = payload.results.categoryId + + then: + payload + } + + def 'can create a child publicationCategory'() { + given: + Map json = [description: 'First child Category', categoryName: 'child1 category1', parentCategoryId: parent] + path('publicationCategory') + ok() + + when: + post(json) + child1 = payload.results.categoryId + + then: + payload + } + + def 'can create a second child publicationCategory'() { + given: + Map json = [description: 'second child Category', categoryName: 'child2 category1', parentCategoryId: parent] + path('publicationCategory') + ok() + + when: + post(json) + child2 = payload.results.categoryId + + then: + payload + } + + def 'can NOT move a Category INSIDE another Category if it would create a cycle'() { + given: + int targetId = child1 + int categoryId = parent + Map body = [targetId: targetId, position: 'INSIDE'] + path([publicationCategory: categoryId], 'move') + invalid() + + when: + put(body) + + then: + payload.messages.find { it.text == 'PublicationCategory may not be moved to create a cycle' } + } + + def 'can move a Category AFTER a SIBLING Category'() { + given: + int categoryId = child1 + int targetId = child2 + Map body = [targetId: targetId, position: 'AFTER'] + path([publicationCategory: categoryId], 'move') + ok() + + when: + put(body) + + then: + Thread.sleep(1000) + payload.results.sequence == 2 + } + + def 'can move a Category BEFORE a SIBLING Category'() { + given: + int categoryId = child1 + int targetId = child2 + Map body = [targetId: targetId, position: 'BEFORE'] + path([publicationCategory: categoryId], 'move') + ok() + + when: + put(body) + + then: + Thread.sleep(1000) + payload.results.sequence == 1 + } + + def 'can move a Category to the first sibling UNDER another Category'() { + given: + int targetId = parent + int categoryId = parent2 + Map body = [targetId: targetId, position: 'INSIDE'] + path([publicationCategory: categoryId], 'move') + ok() + + when: + put(body) + + then: + payload.results.parentCategoryId == targetId + payload.results.sequence == 1 + } + + def 'can move a Category back alongside AFTER its original sibling Category'() { + given: + int targetId = parent + int categoryId = parent2 + Map body = [targetId: targetId, position: 'AFTER'] + path([publicationCategory: categoryId], 'move') + ok() + + when: + put(body) + + then: + payload.results.parentCategoryId == null + } + + def 'can delete child1'() { + given: + path([publicationCategory: child1]) + ok() + + when: + delete() + + then: + !payload + } + + def 'can delete child2'() { + given: + path([publicationCategory: child2]) + ok() + + when: + delete() + + then: + !payload + } + + def 'can delete parent1'() { + given: + path([publicationCategory: parent]) + ok() + + when: + delete() + + then: + !payload + } + + def 'can delete parent2'() { + given: + path([publicationCategory: parent2]) + ok() + + when: + delete() + + then: + !payload + } +} Index: src/integration-test/groovy/com/lemans/ds/search/BrandSearchFunctionalSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/search/BrandSearchFunctionalSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/search/BrandSearchFunctionalSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,95 @@ +package com.lemans.ds.search + +import com.lemans.ds.testing.DsFuncSpec + + +class BrandSearchFunctionalSpec extends DsFuncSpec { + + @Override + String resourceName() { 'brandSearch' } + + def 'can search on brands'() { + given: + String term = 'cy' + queryParams.query = term + path() + ok() + + when: + get() + + then: + with(payload) { + results[0].brandCode == '0116' + results[0].brandId == 116 + results[0].brandName == 'CYCRA' + } + } + + def 'can search on brandCode with wildcard characters'() { + given: + queryParams.pageSize = 50 + queryParams.query = '0*6' + path() + ok() + + when: + get() + + then: + with(payload) { + results.brandCode.every { it.startsWith('0') } + results.brandCode.every { it.endsWith('6') } + } + } + + def 'can search on brandName with wildcard characters'() { + given: + queryParams.pageSize = 50 + queryParams.query = 'p*t*s' + path() + ok() + + when: + get() + + then: + with(payload) { + results.brandName.every { it.startsWith('P') } + results.brandName.every { it.contains('T') } + results.brandName.every { it.endsWith('S') } + } + } + + def 'can search on brandName without wildcard characters'() { + given: + queryParams.pageSize = 50 + path() + ok() + + when: + get() + + then: + with(payload) { + results.brandId + results.brandCode + results.brandName + } + } + + def 'can NOT find a brand that does not exist'() { + given: + queryParams.query = '[___]' + path() + ok() + + when: + get() + + then: + with(payload) { + !results + } + } +} Index: src/integration-test/groovy/com/lemans/ds/search/GenericFilterFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/search/GenericFilterFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/search/GenericFilterFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,97 @@ +package com.lemans.ds.search + +import com.lemans.ds.testing.DsFuncSpec + +class GenericFilterFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'search' } + + def 'can NOT get filters with invalid filterType'() { + given: + queryParams.brandId = 123 + queryParams.derivedPartStatusId = 9001 + String searchType = 'part' + String filterType = 'invalid_type' + path("$searchType/filter/$filterType") + invalid() + + when: + get() + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Invalid Filter Type' + } + } + + def 'can NOT get filters with a filterType that does not match searchType'() { + given: + queryParams.brandId = 123 + queryParams.derivedPartStatusId = 9001 + String searchType = 'product' + String filterType = 'PartsWithoutCategory' + path("$searchType/filter/$filterType") + invalid() + + when: + get() + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Invalid Filter Type' + } + } + + def 'can NOT get filters with invalid searchType'() { + given: + queryParams.brandId = 123 + queryParams.derivedPartStatusId = 9001 + String searchType = '_invalid_' + String filterType = 'PartsWithoutCategory' + path("$searchType/filter/$filterType") + invalid() + + when: + get() + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Invalid Filter Type' + } + } + + def 'can get filters with a filterType'() { + given: + queryParams.derivedPartStatusId = 9001 + queryParams.isDigiActive = 1 + String searchType = 'part' + String filterType = 'PartsWithoutCategory' + path("$searchType/filter/$filterType") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get filters with filterType isDigiActive'() { + given: + queryParams.isDigiActive = 1 + String searchType = 'PRODUCT' + String filterType = 'PRODUCTSWITHOUTMEDIA' + path("$searchType/filter/$filterType") + ok() + + when: + get() + + then: + payload.results + } +} Index: src/integration-test/groovy/com/lemans/ds/search/GenericOptionsFilterFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/search/GenericOptionsFilterFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/search/GenericOptionsFilterFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,587 @@ +package com.lemans.ds.search + +import com.lemans.ds.testing.DsFuncSpec + +@SuppressWarnings(['MethodCount']) +class GenericOptionsFilterFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'search' } + + def 'can NOT get filter options with invalid filterType'() { + given: + queryParams.brandId = 123 + queryParams.derivedPartStatusId = 9001 + queryParams.filterType = 'invalid_type' + String filterName = 'productId' + String searchType = 'part' + path("$searchType/filter/$filterName/options") + invalid() + + when: + get() + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Invalid Filter Type' + } + } + + def 'can NOT get filter options with a filterType that does not match searchType'() { + given: + queryParams.brandId = 123 + queryParams.derivedPartStatusId = 9001 + String searchType = 'product' + queryParams.filterType = 'PartsWithoutCategory' + String filterName = 'productId' + path("$searchType/filter/$filterName/options") + invalid() + + when: + get() + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Invalid Filter Type' + } + } + + def 'can NOT get filter options with invalid searchType'() { + given: + queryParams.brandId = 123 + queryParams.derivedPartStatusId = 9001 + String searchType = '_invalid_' + queryParams.filterType = 'PartsWithoutCategory' + String filterName = 'productId' + path("$searchType/filter/$filterName/options") + invalid() + + when: + get() + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Invalid Filter Type' + } + } + + def 'can NOT get filter options with invalid filterName'() { + given: + queryParams.derivedPartStatusId = 9001 + queryParams.isDigiActive = 1 + String searchType = 'part' + queryParams.filterType = 'PartsWithoutCategory' + String filterName = '_Invalid_' + path("$searchType/filter/$filterName/options") + invalid() + + when: + get() + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Invalid Filter Name' + } + } + + def 'can NOT get filter options with a filterName that does not match searchType or filterType'() { + given: + queryParams.derivedPartStatusId = 9001 + queryParams.isDigiActive = 1 + String searchType = 'product' + queryParams.filterType = 'productsWithOutMedia' + String filterName = 'attributeNameId' + path("$searchType/filter/$filterName/options") + invalid() + + when: + get() + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Invalid Filter Name' + } + } + + def 'can get filter options with a filterType'() { + given: + queryParams.derivedPartStatusId = 9001 + queryParams.isDigiActive = 1 + String searchType = 'part' + queryParams.filterType = 'part' + String filterName = 'productId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get filter options with filterType partsWithoutMedia'() { + given: + queryParams.isDigiActive = 1 + String searchType = 'part' + queryParams.filterType = 'partsWithoutMedia' + String filterName = 'productId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get filter options with filterType partsWithoutMedia and filterName brandId'() { + given: + queryParams.isDigiActive = 1 + String searchType = 'part' + queryParams.filterType = 'partsWithoutMedia' + String filterName = 'brandId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can filter by product'() { + given: + queryParams.isDigiActive = 1 + String searchType = 'product' + String filterName = 'brandId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can filter by filterType product'() { + given: + queryParams.isDigiActive = 1 + String searchType = 'product' + queryParams.filterType = 'product' + String filterName = 'brandId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + // Parts without attribute value + def 'can get BRANDID results in PARTSWITHOUTATTRIBUTEVALUE Queue'() { + given: + queryParams.isDigiActive = 0 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTATTRIBUTEVALUE' + String filterName = 'brandId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get CATEGORY results in PARTSWITHOUTATTRIBUTEVALUE Queue'() { + given: + queryParams.isDigiActive = 0 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTATTRIBUTEVALUE' + String filterName = 'categoryId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get PRODUCT results in PARTSWITHOUTATTRIBUTEVALUE Queue'() { + given: + queryParams.isDigiActive = 0 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTATTRIBUTEVALUE' + String filterName = 'productId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get PARTSTATUS results in PARTSWITHOUTATTRIBUTEVALUE Queue'() { + given: + queryParams.isDigiActive = 0 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTATTRIBUTEVALUE' + String filterName = 'derivedPartStatusId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get PARTSTATUS results in PARTSWITHOUTATTRIBUTEVALUE Queue with MULTISELECT'() { + given: + queryParams.isDigiActive = 0 + queryParams.derivedPartStatusId = 9002 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTATTRIBUTEVALUE' + String filterName = 'categoryId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + // PartsWithoutCategory + def 'can get BRANDID results in PARTSWITHOUTCATEGORY Queue'() { + given: + queryParams.isDigiActive = 0 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTCATEGORY' + String filterName = 'brandId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get VENDORID results in PARTSWITHOUTCATEGORY queue'() { + given: + queryParams.isDigiActive = 0 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTCATEGORY' + String filterName = 'vendorId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get ASSIGNABLECATEGORY results in PARTSWITHOUTCATEGORY queue'() { + given: + queryParams.isDigiActive = 0 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTCATEGORY' + String filterName = 'assignableCategoryId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get DERIVEDPARTSTATUS results in PARTSWITHOUTCATEGORY queue'() { + given: + queryParams.isDigiActive = 0 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTCATEGORY' + String filterName = 'derivedPartStatusId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get SUBCOMCODE results in PARTSWITHOUTCATEGORY queue'() { + given: + queryParams.isDigiActive = 0 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTCATEGORY' + String filterName = 'subComCodeId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get BRANDID results in PARTSWITHOUTCATEGORY Queue with MULTISELECT parameter'() { + given: + queryParams.isDigiActive = 0 + queryParams.derivedPartStatusId = [9001, 9002] // 9001 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTCATEGORY' + String filterName = 'brandId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + // Parts Without Products + + def 'can get BRAND results in PARTSWITHOUTPRODUCT queue'() { + given: + queryParams.isDigiActive = 0 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTPRODUCT' + String filterName = 'brandId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get CATEGORY results in PARTSWITHOUTPRODUCT queue'() { + given: + queryParams.isDigiActive = 0 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTPRODUCT' + String filterName = 'categoryId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get PARTSTATUS results in PARTSWITHOUTPRODUCT queue'() { + given: + queryParams.isDigiActive = 0 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTPRODUCT' + String filterName = 'derivedPartStatusId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get ASSIGNABLEPRODUCT results in PARTSWITHOUTPRODUCT queue'() { + given: + queryParams.isDigiActive = 1 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTPRODUCT' + String filterName = 'assignableProductId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get VENDOR results in PARTSWITHOUTPRODUCT queue'() { + given: + queryParams.isDigiActive = 0 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTPRODUCT' + String filterName = 'vendorId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get VENDOR results in PARTSWITHOUTPRODUCT queue with MULTISELECT'() { + given: + queryParams.isDigiActive = 0 + queryParams.derivedPartStatusId = [9001, 9002] + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTPRODUCT' + String filterName = 'vendorId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get VENDOR results in PARTSWITHOUTPRODUCT queue with different filters'() { + given: + queryParams.isDigiActive = 1 + queryParams.brandId = 7 + queryParams.categoryId = 4804 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTPRODUCT' + String filterName = 'vendorId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + // Products Without Media + + def 'can get PRODUCTID results in PRODUCTSWITHOUTMEDIA queue'() { + given: + queryParams.isDigiActive = 1 + String searchType = 'PRODUCT' + queryParams.filterType = 'PRODUCTSWITHOUTMEDIA' + String filterName = 'productId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get BRANDID results in PRODUCTSWITHOUTMEDIA'() { + given: + queryParams.isDigiActive = 1 + String searchType = 'PRODUCT' + queryParams.filterType = 'PRODUCTSWITHOUTMEDIA' + String filterName = 'brandId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get MEDIATYPE results in PRODUCTWITHOUMEDIA'() { + given: + queryParams.isDigiActive = 1 + String searchType = 'PRODUCT' + queryParams.filterType = 'PRODUCTSWITHOUTMEDIA' + String filterName = 'mediaTypeId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get CATEGORY results in PRODUCTSWITHOUTMEDIA'() { + given: + queryParams.isDigiActive = 1 + String searchType = 'PRODUCT' + queryParams.filterType = 'PRODUCTSWITHOUTMEDIA' + String filterName = 'categoryId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'multiSelect category in partsWithoutAttributeValue'() { + given: + queryParams.categoryId = [4537, 4545] + queryParams.isDigiActive = 1 + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTATTRIBUTEVALUE' + String filterName = 'brandId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } + + def 'multi select category and derived part status in partsWithoutAttributeValue'() { + given: + queryParams.categoryId = [4551, 4552] + queryParams.derivedPartStatusId = [9001, 9002] + String searchType = 'PART' + queryParams.filterType = 'PARTSWITHOUTATTRIBUTEVALUE' + String filterName = 'brandId' + path("$searchType/filter/$filterName/options") + ok() + + when: + get() + + then: + payload.results + } +} Index: src/integration-test/groovy/com/lemans/ds/search/GenericSearchFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/search/GenericSearchFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/search/GenericSearchFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,392 @@ +package com.lemans.ds.search + +import com.lemans.ds.testing.DsFuncSpec + +class GenericSearchFuncSpec extends DsFuncSpec { + + @Override + String resourceName() { 'search' } + + def 'can get search Results with a filterType'() { + given: + queryParams.derivedPartStatusId = 9001 + queryParams.filterType = 'PARTSWITHOUTCATEGORY' + String searchType = 'part' + path("$searchType") + ok() + + when: + get() + + then: + payload.results + payload.meta.totalRecords + } + + def 'can get search Results with Parts MMY filter'() { + given: + queryParams.modelId = 5 + queryParams.startYear = 2000 + queryParams.endYear = 2003 + queryParams.filterType = 'PART' + String searchType = 'part' + path("$searchType") + ok() + + when: + get() + + then: + payload.results + payload.meta.totalRecords + } + + def 'can get partSearchResults by vendorPartNumber'() { + given: + queryParams.vendorPartNumber = 'FE8301-1-2PK' + queryParams.filterType = 'PART' + String searchType = 'part' + path("$searchType") + ok() + + when: + get() + + then: + payload.results + payload.meta.totalRecords + } + + def 'can get search Results with ProductsWithoutMedia isDigiActive filter'() { + given: + queryParams.isDigiActive = true + queryParams.filterType = 'PRODUCTSWITHOUTMEDIA' + String searchType = 'PRODUCT' + path("$searchType") + ok() + + when: + get() + + then: + payload.results + payload.meta.totalRecords + } + + def 'can get search results with attribute name and attribute value filter'() { + given: + queryParams.attributeNameId = 1 + queryParams.attributeValueId = 3 + queryParams.filterType = 'PART' + String searchType = 'part' + path("$searchType") + ok() + + when: + get() + + then: + payload.results + payload.meta.totalRecords >= 9400 + } + + def 'can get partsWithMedia results with attributeName and attributeValue Filter'() { + given: + queryParams.attributeNameId = 910 + queryParams.attributeValueId = '_NO_VALUE_' + queryParams.filterType = 'PARTSWITHOUTMEDIA' + String searchType = 'part' + path("$searchType") + ok() + + when: + get() + + then: + payload.meta.totalRecords >= 160 + } + + def 'can get partsWithoutCategory results with partDescr Filter'() { + given: + queryParams.partDescr = 'helmet' + queryParams.filterType = 'PARTSWITHOUTCATEGORY' + String searchType = 'part' + path("$searchType") + ok() + + when: + get() + + then: + payload.meta.totalRecords >= 7000 + } + + def 'can get search results filtered by vendorId'() { + given: + queryParams.vendorId = '1646-A' + queryParams.filterType = 'PARTSWITHOUTPRODUCT' + String searchType = 'part' + path("$searchType") + ok() + + when: + get() + + then: + payload.meta.totalRecords >= 40 + } + + def 'can get search results filtered by vendorPartNumber'() { + given: + queryParams.vendorPartNumber = 'KG0025HPK' + queryParams.filterType = 'PARTSWITHOUTATTRIBUTEVALUE' + String searchType = 'part' + path("$searchType") + ok() + + when: + get() + + then: + payload.meta.totalRecords == 0 + } + + def 'can not get results with invalid Search value'() { + given: + queryParams.attributeNameId = 1 + queryParams.attributeValueId = 3 + queryParams.filterType = 'PARTSWITHOUTMEDIA' + String searchType = 'product' + path("$searchType") + invalid() + + when: + get() + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Invalid Filter Type' + } + } + + def 'can get search results with attribute name and attribute value with flag tab results'() { + given: + queryParams.mode = 'Flag' + queryParams.attributeNameId = 1 + queryParams.attributeValueId = 3 + queryParams.filterType = 'PART' + String searchType = 'part' + path("$searchType") + ok() + + when: + get() + + then: + payload.results + payload.meta.totalRecords >= 9400 + } + + def 'can NOT get search Results with invalid filterType'() { + given: + queryParams.brandId = 123 + queryParams.derivedPartStatusId = 9001 + String searchType = 'part' + queryParams.filterType = '_invalid_' + path("$searchType") + invalid() + + when: + get() + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Invalid Filter Type' + } + } + + def 'can NOT get search Results with invalid searchType'() { + given: + queryParams.brandId = 123 + queryParams.derivedPartStatusId = 9001 + String searchType = '_invalid_' + queryParams.filterType = 'PARTSWITHOUTCATEGORY' + path("$searchType") + invalid() + + when: + get() + + then: + with(payload.messages[0]) { + type == 'error' + text == 'Invalid Filter Type' + } + } + + def 'can get tab output'() { + given: + queryParams.mode = 'Flag' + String searchType = 'part' + path("$searchType") + ok() + + when: + get() + + then: + payload.results[0] + } + + def 'can get tab output with more queryParams'() { + given: + queryParams.mode = 'Flag' + queryParams.brandId = 123 + queryParams.derivedPartStatusId = 9001 + String searchType = 'part' + path("$searchType") + ok() + + when: + get() + + then: + payload.results[0] + } + + def 'can get parts when mode is null'() { + given: + String searchType = 'part' + path("$searchType") + ok() + + when: + get() + + then: + payload.results + payload.meta + } + + def 'can not get Flag tab ed results with invalid searchType'() { + given: + String searchType = '_invalid_' + queryParams.mode = 'Flag' + path("$searchType") + invalid() + + when: + get() + + then: + payload.with { + messages[0].type == 'error' + messages[0].text == 'Invalid Filter Type' + } + } + + def 'can not get results if no parts available'() { + given: + queryParams.brandId = 0002 + queryParams.partNumber = 'sfdfdgfdgfdg' + String searchType = 'part' + queryParams.mode = 'Flag' + path("$searchType") + ok() + + when: + get() + + then: + payload.with { + results == [] + meta.totalRecords == 0 + } + } + + def 'can get results with multiple partStatus in partSearch'() { + queryParams.derivedPartStatusId = [9001, 9002, 9003] + String searchType = 'part' + path("$searchType") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get results with multiple partStatus in partsWihoutCategory'() { + queryParams.derivedPartStatusId = [9001, 9002, 9003] + String searchType = 'part' + queryParams.filterType = 'PARTSWITHOUTCATEGORY' + path("$searchType") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get results with multiple partStatus in partsWithoutProduct'() { + queryParams.derivedPartStatusId = [9001, 9002, 9003] + String searchType = 'part' + queryParams.filterType = 'PARTSWITHOUTPRODUCT' + path("$searchType") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get results with multiple partStatus in partsWithoutAttributeValue'() { + queryParams.derivedPartStatusId = [9001, 9002, 9003] + String searchType = 'part' + queryParams.filterType = 'PARTSWITHOUTATTRIBUTEVALUE' + path("$searchType") + ok() + + when: + get() + + then: + payload.results + } + + def 'can get results with multiple partStatus in partsWithoutMedia'() { + queryParams.derivedPartStatusId = [9001, 9002, 9003] + String searchType = 'part' + queryParams.filterType = 'PARTSWITHOUTMEDIA' + path("$searchType") + ok() + + when: + get() + + then: + payload.results + } + + def 'multi Select category partsWithoutAttributeValue'() { + queryParams.categoryId = [4544, 4545] + queryParams.isDigiActive = 1 + String searchType = 'part' + queryParams.filterType = 'PARTSWITHOUTATTRIBUTEVALUE' + path("$searchType") + ok() + + when: + get() + + then: + payload.results + } +} Index: src/integration-test/groovy/com/lemans/ds/testing/DsFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/ds/testing/DsFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/ds/testing/DsFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,18 @@ +package com.lemans.ds.testing + +import com.lemans.testing.LemansApiFunctionalSpec +import ds.service.Application +import grails.test.mixin.integration.Integration +import groovyx.net.http.HTTPBuilder + +@Integration(applicationClass = Application) +abstract class DsFuncSpec extends LemansApiFunctionalSpec { + + @Override + final String serviceName() { 'ds-service' } + + def setup() { + port = serverPort + http = new HTTPBuilder(url()) + } +} Index: src/integration-test/groovy/com/lemans/security/TokenVerifierFuncSpec.groovy =================================================================== diff -u --- src/integration-test/groovy/com/lemans/security/TokenVerifierFuncSpec.groovy (revision 0) +++ src/integration-test/groovy/com/lemans/security/TokenVerifierFuncSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,20 @@ +package com.lemans.security + +import com.lemans.ds.testing.DsFuncSpec + +class TokenVerifierFuncSpec extends DsFuncSpec { + + def 'can NOT act without a valid security token'() { + given: + System.setProperty('ignoreToken', '') + domain = null + path() + expectedStatusCode = 401 + + when: + get() + + then: + payload.messages[0].text == 'no credentials' + } +} Index: src/main/groovy/com/lemans/FeatureToggles.groovy =================================================================== diff -u --- src/main/groovy/com/lemans/FeatureToggles.groovy (revision 0) +++ src/main/groovy/com/lemans/FeatureToggles.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,12 @@ +package com.lemans + +class FeatureToggles { + + static final Boolean getOPT_LOCKING_FEATURE_TOGGLE() { + System.getProperty('OPT_LOCKING') + } + + static final Boolean getUS_EU_SYNC_FEATURE_TOGGLE() { + System.getProperty('US_EU_SYNC') + } +} Index: src/main/groovy/com/lemans/ds/DSLocale.groovy =================================================================== diff -u --- src/main/groovy/com/lemans/ds/DSLocale.groovy (revision 0) +++ src/main/groovy/com/lemans/ds/DSLocale.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,20 @@ +package com.lemans.ds + +import com.lemans.services.Auditable + +/** + * Created by MUmachi on 9/28/2017. + */ +abstract class DSLocale extends Auditable { + + String locale + + static constraints = { + + + locale maxSize: 2, inList: ['de', 'it', 'es', 'fr'] + + } + +} + Index: src/main/groovy/com/lemans/ds/MoveCategoryCommand.groovy =================================================================== diff -u --- src/main/groovy/com/lemans/ds/MoveCategoryCommand.groovy (revision 0) +++ src/main/groovy/com/lemans/ds/MoveCategoryCommand.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,26 @@ +package com.lemans.ds + +import com.lemans.FeatureToggles +import grails.validation.Validateable +import groovy.transform.ToString + +@ToString +class MoveCategoryCommand implements Validateable { + + static final String BEFORE = 'BEFORE' + static final String AFTER = 'AFTER' + static final String INSIDE = 'INSIDE' + + static final List POSITIONS = [BEFORE, AFTER, INSIDE].asImmutable() + + Integer targetId + + String position + + Integer version + + static constraints = { + position blank: false, inList: POSITIONS + version nullable: !FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE + } +} Index: src/main/groovy/com/lemans/ds/MoveCommand.groovy =================================================================== diff -u --- src/main/groovy/com/lemans/ds/MoveCommand.groovy (revision 0) +++ src/main/groovy/com/lemans/ds/MoveCommand.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,28 @@ +package com.lemans.ds + +import com.lemans.FeatureToggles +import grails.validation.Validateable + + +class MoveCommand implements Validateable { + + static final String BEFORE = 'BEFORE' + static final String AFTER = 'AFTER' + static final String INSIDE = 'INSIDE' + + static final List POSITIONS = [BEFORE, AFTER, INSIDE].asImmutable() + + Integer targetId + + String position + + Integer sourceVersion + + Integer targetVersion + + static constraints = { + position blank: false, inList: POSITIONS + sourceVersion nullable: !FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE + targetVersion nullable: !FeatureToggles.OPT_LOCKING_FEATURE_TOGGLE + } +} Index: src/main/groovy/com/lemans/ds/bulk/excel/ExcelBuilderHelper.groovy =================================================================== diff -u --- src/main/groovy/com/lemans/ds/bulk/excel/ExcelBuilderHelper.groovy (revision 0) +++ src/main/groovy/com/lemans/ds/bulk/excel/ExcelBuilderHelper.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,146 @@ +package com.lemans.ds.bulk.excel + +import groovy.util.logging.Slf4j +import org.apache.poi.hssf.usermodel.HSSFCell +import org.apache.poi.hssf.usermodel.HSSFCellStyle +import org.apache.poi.hssf.usermodel.HSSFFont +import org.apache.poi.hssf.usermodel.HSSFHyperlink +import org.apache.poi.hssf.usermodel.HSSFRow +import org.apache.poi.hssf.usermodel.HSSFSheet +import org.apache.poi.hssf.usermodel.HSSFWorkbook +import org.apache.poi.hssf.util.HSSFColor +import org.apache.poi.ss.usermodel.Cell +import org.apache.poi.ss.usermodel.Hyperlink + +@Slf4j +class ExcelBuilderHelper { + + void reportBySheet(List results, List identifier, HSSFWorkbook reportBook, String sheetName, String filePath) { + if (sheetName in ['CategoryAttributeValue', 'Part']) { + generateExcelReport(results, identifier, reportBook, sheetName, filePath) + } else { + List dataWithReport = results.findAll { it.report != [] } + if (dataWithReport) { + generateExcelReport(dataWithReport, identifier, reportBook, sheetName, filePath) + } + } + } + + @SuppressWarnings(['CatchException']) + void generateExcelReport(List results, List identifier, HSSFWorkbook reportBook, String sheetName, String filePath) { + FileOutputStream fileOutputStream = new FileOutputStream(filePath) + HSSFCellStyle defaultStyle = defaultStyle(reportBook) + try { + HSSFSheet reportSheet = reportBook.createSheet(sheetName) + addHeaderRow(identifier, reportSheet, defaultStyle, sheetName) + addRows(results, reportBook, reportSheet) + reportBook.write(fileOutputStream) + fileOutputStream.close() + } catch (Exception e) { + log.error e.message, e + } + fileOutputStream.flush() + fileOutputStream.close() + } + + private void addHeaderRow(List identifier, HSSFSheet reportSheet, HSSFCellStyle defaultStyle, String sheetName) { + isReportColumnAdded(sheetName, identifier) + HSSFRow reportRow = reportSheet.createRow(0) + HSSFCell reportCell + identifier.eachWithIndex { def entry, int i -> + reportCell = reportRow.createCell(i) + reportCell.cellValue = entry.toString() + reportCell.cellStyle = defaultStyle + } + } + + private void isReportColumnAdded(String sheetName, List identifier) { + if (!(sheetName in ['CategoryAttributeValue', 'Part'])) { + if (!('report' in identifier)) { identifier.add('report') } + } + } + + private void addRows(List errorData, HSSFWorkbook reportBook, HSSFSheet reportSheet) { + HSSFCellStyle rowStyle = rowStyle(reportBook) + errorData.eachWithIndex { Map data, int index -> + HSSFRow reportRow = reportSheet.createRow(index + 1) + data.eachWithIndex { def entry, int idx -> + HSSFCell reportCell = reportRow.createCell(idx) + reportCell.cellStyle = rowStyle + addCellValue(entry.value, reportCell, reportBook) + reportSheet.autoSizeColumn(idx) + } + } + } + + private void addCellValue(Cell cell, Cell reportCell, HSSFWorkbook reportBook) { + copyCell(cell, reportCell, reportBook) + } + + @SuppressWarnings(['UnusedPrivateMethodParameter']) + private void addCellValue(cellValue, HSSFCell reportCell, HSSFWorkbook reportBook) { + if (cellValue.toString() == '') { + reportCell.cellValue = '' + } else { + reportCell.cellValue = cellValue.toString() + } + } + + private void copyCell(Cell oldCell, Cell newCell, HSSFWorkbook reportBook) { + switch (oldCell.cellType) { + case Cell.CELL_TYPE_STRING: + newCell.cellValue = oldCell.stringCellValue + break + case Cell.CELL_TYPE_NUMERIC: + newCell.cellValue = oldCell.numericCellValue + break + case Cell.CELL_TYPE_BLANK: + newCell.cellType = Cell.CELL_TYPE_BLANK + break + case Cell.CELL_TYPE_BOOLEAN: + newCell.cellValue = oldCell.booleanCellValue + break + case Cell.CELL_TYPE_ERROR: + newCell.cellErrorValue = oldCell.errorCellValue + break + case Cell.CELL_TYPE_FORMULA: + newCell.cellFormula = oldCell.cellFormula + break + default: + break + } + if (oldCell.hyperlink) { + addHyperLink(oldCell, newCell, reportBook) + } + } + + private void addHyperLink(Cell value, HSSFCell reportCell, HSSFWorkbook reportBook) { + HSSFCellStyle hyperLinkStyle = hyperLinkStyle(reportBook) + HSSFHyperlink hyperlink = (HSSFHyperlink) reportBook.creationHelper.createHyperlink(Hyperlink.LINK_URL) + hyperlink.address = value.hyperlink.address + reportCell.hyperlink = (HSSFHyperlink) hyperlink + reportCell.cellStyle = hyperLinkStyle + } + + private HSSFCellStyle defaultStyle(HSSFWorkbook reportBook) { + HSSFCellStyle defaultStyle = reportBook.createCellStyle() + HSSFFont defaultFont = reportBook.createFont() + defaultFont.bold = true + defaultStyle.font = defaultFont + defaultStyle + } + + private HSSFCellStyle hyperLinkStyle(HSSFWorkbook reportBook) { + HSSFCellStyle style = reportBook.createCellStyle() + HSSFFont font = reportBook.createFont() + font.color = HSSFColor.BLUE.index + style.font = font + style + } + + private HSSFCellStyle rowStyle(HSSFWorkbook reportBook) { + HSSFCellStyle style = reportBook.createCellStyle() + style.wrapText = true + style + } +} Index: src/main/groovy/com/lemans/ds/bulk/excel/ExcelParserHelper.groovy =================================================================== diff -u --- src/main/groovy/com/lemans/ds/bulk/excel/ExcelParserHelper.groovy (revision 0) +++ src/main/groovy/com/lemans/ds/bulk/excel/ExcelParserHelper.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,68 @@ +package com.lemans.ds.bulk.excel + +import org.apache.commons.io.FilenameUtils +import org.apache.poi.hssf.usermodel.HSSFWorkbook +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 + +class ExcelParserHelper { + + private static final Map IMPORT_TYPE_SHEET_NAME_MAPPING = [PRODUCT: ['Product', 'ProductFeature'], + CATEGORYATTRIBUTE: ['CategoryAttributeValue'], PART: ['Part']] + + + Workbook workBook(String filesPath) { + File[] files = new File(filesPath).listFiles() + String filename = files.first() + FilenameUtils.getExtension(filename).equalsIgnoreCase('xls') ? + new HSSFWorkbook(files.first().newInputStream()) : new XSSFWorkbook(files.first().newInputStream()) + } + + List collectSheetsFromExcel(Workbook inputFile, String importType) { + List sheets = [] + inputFile.sheets.each { Sheet sheet -> + if (sheet.sheetName in IMPORT_TYPE_SHEET_NAME_MAPPING[importType.toUpperCase()] && sheet.lastRowNum > 0) { + sheets << rowsOfSheet(sheet) + } + } + sheets + } + + private Map rowsOfSheet(Sheet sheet) { + List rows = collectRows(sheet) + List header = discardReportHeaderIfPresent((List) rows.get(0)) + List data = transposeHeaderWithRows(rows) + [data: data, sheetName: sheet.sheetName, identifier: header] + } + + private List collectRows(Sheet sheet) { + Integer noOfColumns = sheet.getRow(0).size() + List rows = [] + for (Row row : sheet) { + List cellsOfRows = [] + noOfColumns.times { + cellsOfRows << row.getCell(it, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK) + } + rows << cellsOfRows + } + rows + } + + private List transposeHeaderWithRows(List rows) { + List data = [] + List headerRow = (List) rows.get(0).collect { it.toString().replaceAll('\\s', '') } + for (int i = 1; i < rows.size(); i++) { + Map map = [headerRow, rows.get(i)].transpose().collectEntries { [(it[0].toString()): it[1]] } + map.remove('report') + data << map + } + data + } + + private List discardReportHeaderIfPresent(List header) { + header.removeIf { it.toString() == 'report' } + header + } +} Index: src/main/groovy/com/lemans/ds/part/PartAttributeTransformer.groovy =================================================================== diff -u --- src/main/groovy/com/lemans/ds/part/PartAttributeTransformer.groovy (revision 0) +++ src/main/groovy/com/lemans/ds/part/PartAttributeTransformer.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,44 @@ +package com.lemans.ds.part + +import groovy.xml.MarkupBuilder + +class PartAttributeTransformer { + + String partListToXML(List partNumbers) { + StringWriter writer = new StringWriter() + new MarkupBuilder(writer).root { + part { + partNumbers.each { partNumber(it) } + } + } + writer + } + + String partAttributeToXML(Map partAttributeInput) { + StringWriter writer = new StringWriter() + new MarkupBuilder(writer).root { + action(value: partAttributeInput.operation) { + part { + partAttributeInput.partNumber.each { partNumber(it) } + } + if (!partAttributeInput.attribute?.isEmpty()) { + attributes { + partAttributeInput.attribute.each { attribName -> + attribute { + attributeNameId(attribName.attributeNameId) + if (!attribName.attributeValueIds?.isEmpty()) { + values { + attribName.attributeValueIds.each { + attributeValueId(it) + } + } + } + } + } + } + } + } + } + writer + } +} Index: src/main/groovy/com/lemans/ds/product/CategoryProductXmlGenerator.groovy =================================================================== diff -u --- src/main/groovy/com/lemans/ds/product/CategoryProductXmlGenerator.groovy (revision 0) +++ src/main/groovy/com/lemans/ds/product/CategoryProductXmlGenerator.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,26 @@ +package com.lemans.ds.product + +import groovy.xml.MarkupBuilder + + +class CategoryProductXmlGenerator { + + String generateProductCategoryXml(Map input) { + StringWriter writer = new StringWriter() + new MarkupBuilder(writer).root { + action(value: input.operation) { + categoryId(input.categoryId) + products { + if (input.productIds && !input.partNumbers?.isEmpty()) { + input.productIds.each { id -> + productId(id) + } + } else if (input.productId) { + productId { partNumber(input.productId) } + } + } + } + } + writer + } +} Index: src/main/groovy/com/lemans/ds/product/ProductFeaturesXmlTransformer.groovy =================================================================== diff -u --- src/main/groovy/com/lemans/ds/product/ProductFeaturesXmlTransformer.groovy (revision 0) +++ src/main/groovy/com/lemans/ds/product/ProductFeaturesXmlTransformer.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,27 @@ +package com.lemans.ds.product + +import groovy.xml.MarkupBuilder + +class ProductFeaturesXmlTransformer { + + String toXml(Map values) { + StringWriter writer = new StringWriter() + new MarkupBuilder(writer).root { + action(value: 'INSERT') { + product { + catalogInstanceId(values.catalogInstanceId) + productId(values.productId) + } + features { + values.features.each { f -> + feature { + featureTypeId(f.featureTypeId) + featureText(f.featureText) + } + } + } + } + } + writer.toString() + } +} Index: src/main/groovy/com/lemans/ds/product/ProductPartTransformer.groovy =================================================================== diff -u --- src/main/groovy/com/lemans/ds/product/ProductPartTransformer.groovy (revision 0) +++ src/main/groovy/com/lemans/ds/product/ProductPartTransformer.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,45 @@ +package com.lemans.ds.product + +import groovy.xml.MarkupBuilder + + +class ProductPartTransformer { + + String generateProductPartXml(Map input) { + StringWriter writer = new StringWriter() + new MarkupBuilder(writer).root { + action(value: input.operation) { + product { + productId(input.productId) + } + parts { + if (input.partNumbers && !input.partNumbers?.isEmpty()) { + input.partNumbers.each { pNum -> + part { partNumber(pNum) } + } + } else if (input.partNumber) { + part { partNumber(input.partNumber) } + } + } + } + } + writer + } + + String generateProductFlagXml(Map input) { + StringWriter writer = new StringWriter() + new MarkupBuilder(writer).root { + inputParameters { + if (input.productIds && !input.productIds?.isEmpty()) { + + input.productIds.each { pId -> + productId(pId) + } + } else if (input.productId) { + productId(input.productId) + } + } + } + writer + } +} Index: src/main/groovy/com/lemans/ds/rabbit/QueueRequestType.groovy =================================================================== diff -u --- src/main/groovy/com/lemans/ds/rabbit/QueueRequestType.groovy (revision 0) +++ src/main/groovy/com/lemans/ds/rabbit/QueueRequestType.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,30 @@ +package com.lemans.ds.rabbit + +enum QueueRequestType { + + CATEGORY_CREATE, + CATEGORY_UPDATE, + CATEGORY_DELETE, + CATEGORY_MOVE, + + CATEGORY_ATTRIBUTE_CREATE, + CATEGORY_ATTRIBUTE_UPDATE, + CATEGORY_ATTRIBUTE_DELETE, + CATEGORY_ATTRIBUTE_MOVE, + + CATEGORY_ATTRIBUTE_VALUE_CREATE, + CATEGORY_ATTRIBUTE_VALUE_UPDATE, + CATEGORY_ATTRIBUTE_VALUE_DELETE, + CATEGORY_ATTRIBUTE_VALUE_MOVE, + + CATEGORY_SUB_COM_CODE_CREATE, + CATEGORY_SUB_COM_CODE_DELETE, + + ATTRIBUTE_NAME_CREATE, + ATTRIBUTE_NAME_UPDATE, + ATTRIBUTE_NAME_DELETE, + + ATTRIBUTE_VALUE_CREATE, + ATTRIBUTE_VALUE_UPDATE, + ATTRIBUTE_VALUE_DELETE, +} Index: src/main/groovy/com/lemans/ds/search/FilterXmlGenerator.groovy =================================================================== diff -u --- src/main/groovy/com/lemans/ds/search/FilterXmlGenerator.groovy (revision 0) +++ src/main/groovy/com/lemans/ds/search/FilterXmlGenerator.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,76 @@ +package com.lemans.ds.search + +import groovy.xml.MarkupBuilder + + +class FilterXmlGenerator { + + private static final List PART_FILTERS = ['partNumber', 'partDescr', 'partStatusCode', 'derivedPartStatusId', + 'brandId', 'vendorId', 'vendorPartNumber', 'categoryId', 'productId', 'subComCodeId', + 'isDigiActive', 'modelId', 'attributeNameId', 'attributeValueId', 'startYear', 'endYear'] + + private static final List PARTS_WITH_OUT_CATEGORIES_FILTERS = ['assignableCategoryId', 'brandId', 'vendorId', 'derivedPartStatusId', + 'subComCodeId', 'isDigiActive', 'partDescr'] + private static final List PARTS_WITH_OUT_ATTRIBUTE_VALUES_FILTERS = ['brandId', 'categoryId', 'productId', 'derivedPartStatusId', + 'partDescr', 'isDigiActive', 'vendorPartNumber'] + private static final List PARTS_WITH_OUT_PRODUCTS_FILTERS = ['assignableProductId', 'brandId', 'categoryId', 'derivedPartStatusId', + 'partDescr', 'isDigiActive', 'vendorPartNumber', 'vendorId'] + + private static final List PARTS_WITH_OUT_MEDIA_FILTERS = ['partDescr', 'isDigiActive', 'mediaTypeId', 'derivedPartStatusId', + 'categoryId', 'brandId', 'productId', 'attributeNameId', 'attributeValueId'] + + private static final List PRODUCTS_WITH_OUT_MEDIA_FILTERS = ['productId', 'productName', 'categoryId', 'brandId', 'mediaTypeId', + 'isDigiActive'] + + private static final List PRODUCT_FILTER = ['categoryId', 'brandId', 'isDigiActive', 'flagId'] + + static final Map SEARCH_FILTER_TYPE_MAPPING = + [ + 'PART': [ + 'PART': PART_FILTERS, + 'PARTSWITHOUTCATEGORY': PARTS_WITH_OUT_CATEGORIES_FILTERS, + 'PARTSWITHOUTATTRIBUTEVALUE': PARTS_WITH_OUT_ATTRIBUTE_VALUES_FILTERS, + 'PARTSWITHOUTPRODUCT': PARTS_WITH_OUT_PRODUCTS_FILTERS, + 'PARTSWITHOUTMEDIA': PARTS_WITH_OUT_MEDIA_FILTERS + ], + 'PRODUCT': [ + 'PRODUCT': PRODUCT_FILTER, + 'PRODUCTSWITHOUTMEDIA': PRODUCTS_WITH_OUT_MEDIA_FILTERS + ] + ] + + String generateXml(String searchType, String filterType, Map filterInput) { + List filters = SEARCH_FILTER_TYPE_MAPPING[searchType.toUpperCase()]?."${filterType.toUpperCase()}" + if (filters) { + StringWriter writer = new StringWriter() + new MarkupBuilder(writer).root { + inputParameters { + filters.each { filter -> + if (FIELDS_WITH_MULTIPLE_SELECTIONS.contains(filter)) { + if (filterInput[filter]) { + "${filter}s" { + filterInput[filter].each { id -> + "$filter"(id) + } + } + } + } else { + def value = filterInput[filter] + if ((value != null) && (value != '')) { + "$filter"(value) + } + } + } + } + if (filterInput.filterName) { + outputFilters { + filter(filterInput.filterName) + } + } + } + writer + } + } + + private static final List FIELDS_WITH_MULTIPLE_SELECTIONS = ['derivedPartStatusId', 'categoryId'] +} Index: src/main/groovy/com/lemans/reporting/SsrReporter.groovy =================================================================== diff -u --- src/main/groovy/com/lemans/reporting/SsrReporter.groovy (revision 0) +++ src/main/groovy/com/lemans/reporting/SsrReporter.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,86 @@ +package com.lemans.reporting + +import groovy.util.logging.Log4j +import org.apache.http.HttpResponse +import org.apache.http.client.methods.HttpGet +import org.apache.http.protocol.BasicHttpContext + +@Log4j +@SuppressWarnings(['CatchException', 'PrintStackTrace']) +class SsrReporter { + + def httpClient + + def sqlServerReportApiAuth + + def sqlServerReportHost + + /** + * Render PDF or EXCEL SSRS report. + * + * @param stemUrl + * @param reportFormat, optional, default is EXCEL + * @param response to write to + */ + void renderSSRSReport(String stemUrl, String reportFormat, response) { + String url = createSSRUrl(stemUrl, reportFormat) + log.debug 'url:' + url + BasicHttpContext localContext = new BasicHttpContext() + HttpGet httpMethod = createSSRGet(url) + try { + HttpResponse httpResponse = httpClient.execute(httpMethod, localContext) + if (httpResponse.statusLine.statusCode == 200) { + if (httpResponse.entity) { + response.contentType = httpResponse.getFirstHeader('Content-Type')?.value + response.setHeader('Content-Disposition', httpResponse.getFirstHeader('Content-Disposition')?.value) + response.outputStream << httpResponse.entity?.content + response.outputStream.flush() + } + } else { + String msg = 'failed request to sql server reporting service :' + httpResponse.statusLine + log.error msg + throw new IllegalStateException(msg) + } + } catch (Exception exp) { + exp.printStackTrace() + throw new IllegalStateException(exp) + } finally { + httpMethod.releaseConnection() + } + } + + /** + * Creates a url parameter String from the List of reportParams and request params. + * + * @param reportParams + * @param params + * + * @return url parameter String + */ + String createUrlParams(List reportParams, Map params) { + reportParams.inject(new StringBuilder()) { urlParams, param -> + String value = params[param] + String newValue = '' + if (value) { + newValue = URLEncoder.encode(value, 'UTF-8') + } + urlParams << "&$param" << (value ? "=$newValue" : ':isnull=true') + } + } + + private String createSSRUrl(stemUrl, String reportFormat) { + String formatParams = '&rs:Command=Render&rs:ClearSession=true' + if (reportFormat) { formatParams += "&rs:format=${reportFormat}" } + else { formatParams += '&rs:format=EXCEL' } + "${sqlServerReportHost}/reportserver?${stemUrl}${formatParams}" + } + + private HttpGet createSSRGet(String url) { + HttpGet httpMethod = new HttpGet(url) + httpMethod.params.setParameter('http.socket.timeout', 120 * 1000) + httpMethod.params.setParameter('http.connection.timeout', 20 * 1000) + httpMethod.setHeader('Authorization', "Basic $sqlServerReportApiAuth") + httpMethod + } + +} Index: src/test/groovy/com/lemans/ds/MoveCommandSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/MoveCommandSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/MoveCommandSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,37 @@ +package com.lemans.ds + +import spock.lang.Specification +import spock.lang.Unroll + +class MoveCommandSpec extends Specification { + + MoveCommand cmd = new MoveCommand(targetId: 54321, position: 'BEFORE') + + def 'a valid MoveCommand has no errors'() { + expect: + cmd.validate() + } + + + @Unroll + def 'MoveCommand with position #position validation error is #error'() { + given: + cmd.position = position + + when: + cmd.validate() + + then: + error == cmd.errors['position']?.code + + where: + position || error + null || 'nullable' + '' || 'blank' + 'BEFORE' || null + 'AFTER' || null + 'INSIDE' || null + 'cvsdgc' || 'not.inList' + } + +} Index: src/test/groovy/com/lemans/ds/attribute/AttributeNameSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/attribute/AttributeNameSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/attribute/AttributeNameSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,49 @@ +package com.lemans.ds.attribute + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor +import spock.lang.Shared +import spock.lang.Unroll + +@TestFor(AttributeName) +class AttributeNameSpec extends Auditable32Spec { + + @Override + protected getAuditable() { attributeName } + + AttributeName attributeName = new AttributeName(id: 1, attributeName: 'Size') + + @Shared + String tooLong = 'This is long text of 102 characters, This is long text of 102 characters, so it is really a long text.' + + @Shared + String ok = 'Width' + + def 'can create a valid AttributeName'() { + when: + auditable.validate() + + then: + !auditable.errors.hasErrors() + } + + @Unroll + def 'AttributeName with filter #filter #value validation error is #error'() { + given: + attributeName[filter] = value + + when: + attributeName.validate() + + then: + error == attributeName.errors[filter]?.code + + where: + filter | value | error + 'attributeName' | tooLong | MAX_SIZE + 'attributeName' | ok | null + 'attributeDisplayName' | tooLong | MAX_SIZE + 'attributeDisplayName' | ok | null + 'attributeDisplayName' | null | null + } +} Index: src/test/groovy/com/lemans/ds/attribute/AttributeValueSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/attribute/AttributeValueSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/attribute/AttributeValueSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,50 @@ +package com.lemans.ds.attribute + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor +import org.apache.commons.lang.StringUtils +import spock.lang.Shared +import spock.lang.Unroll + +/** + * See the API for {@link grails.test.mixin.domain.DomainClassUnitTestMixin} for usage instructions + */ +@TestFor(AttributeValue) +class AttributeValueSpec extends Auditable32Spec { + + @Override + protected getAuditable() { attributeValue } + + AttributeValue attributeValue = new AttributeValue(id: 1, attributeValue: 'Size') + + @Shared + String tooLong = StringUtils.repeat('value', 151) + + @Shared + String ok = 'Small' + + def 'can create a valid AttributeValue'() { + when: + auditable.validate() + + then: + !auditable.errors.hasErrors() + } + + @Unroll + def 'AttributeValue with filter #filter #value validation error is #error'() { + given: + attributeValue[filter] = value + + when: + attributeValue.validate() + + then: + error == attributeValue.errors[filter]?.code + + where: + filter | value | error + 'attributeValue' | tooLong | MAX_SIZE + 'attributeValue' | ok | null + } +} Index: src/test/groovy/com/lemans/ds/category/CategoryLocaleSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/category/CategoryLocaleSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/category/CategoryLocaleSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,21 @@ +package com.lemans.ds.category + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor + +@TestFor(CategoryLocale) +class CategoryLocaleSpec extends Auditable32Spec { + + @Override + protected Object getAuditable() { categoryLocale } + + CategoryLocale categoryLocale = new CategoryLocale(categoryId: 1, locale: 'de') + + def 'can create a valid categorylocale'() { + when: + auditable.validate() + + then: + !auditable.errors.hasErrors() + } +} Index: src/test/groovy/com/lemans/ds/category/CategorySpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/category/CategorySpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/category/CategorySpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,61 @@ +package com.lemans.ds.category + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor +import spock.lang.Unroll + +@TestFor(Category) +class CategorySpec extends Auditable32Spec { + + @Override + protected Object getAuditable() { category } + + Category category = new Category(categoryName: 'A', catalogInstanceId: 0, sequence: 1) + + def 'can create a valid Category'() { + when: + auditable.validate() + + then: + !auditable.errors.hasErrors() + } + + @Unroll + def 'categoryName with value #value validation error is #error'() { + given: + auditable.categoryName = value + + when: + auditable.validate() + + then: + auditable.errors['categoryName']?.code == error + + where: + value || error + null || 'nullable' + '' || 'blank' + 'a' || null + '100chars'.padRight(100, '_') || null + '101chars'.padRight(101, '_') || MAX_SIZE + } + + @Unroll + def 'description with value #value validation error is #error'() { + given: + auditable.description = value + + when: + auditable.validate() + + then: + auditable.errors['description']?.code == error + + where: + value || error + null || null + 'a' || null + '2000chars'.padRight(2000, '_') || null + '2001chars'.padRight(2001, '_') || MAX_SIZE + } +} Index: src/test/groovy/com/lemans/ds/category/MoveCategoryCommandSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/category/MoveCategoryCommandSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/category/MoveCategoryCommandSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,36 @@ +package com.lemans.ds.category + +import com.lemans.ds.MoveCategoryCommand +import spock.lang.Specification +import spock.lang.Unroll + +class MoveCategoryCommandSpec extends Specification { + + MoveCategoryCommand cmd = new MoveCategoryCommand(targetId: 54321, position: 'BEFORE') + + def 'a valid MoveCategoryCommand has no errors'() { + expect: + cmd.validate() + } + + @Unroll + def 'MoveCategoryCommand with position #position validation error is #error'() { + given: + cmd.position = position + + when: + cmd.validate() + + then: + error == cmd.errors['position']?.code + + where: + position || error + null || 'nullable' + '' || 'blank' + 'BEFORE' || null + 'AFTER' || null + 'INSIDE' || null + 'cvsdgc' || 'not.inList' + } +} Index: src/test/groovy/com/lemans/ds/explosion/ExplosionDiagramSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/explosion/ExplosionDiagramSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/explosion/ExplosionDiagramSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,21 @@ +package com.lemans.ds.explosion + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor + +@TestFor(ExplosionDiagram) +class ExplosionDiagramSpec extends Auditable32Spec { + + @Override + protected Object getAuditable() { explosionDiagram } + + ExplosionDiagram explosionDiagram = new ExplosionDiagram(title: 'test', jsonData: 'result', isActive: 1) + + def 'can create a valid explosionDiagram'() { + when: + auditable.validate() + + then: + !auditable.errors.hasErrors() + } +} Index: src/test/groovy/com/lemans/ds/fitment/MakeSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/fitment/MakeSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/fitment/MakeSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,61 @@ +package com.lemans.ds.fitment + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor +import spock.lang.Unroll + +@TestFor(Make) +class MakeSpec extends Auditable32Spec { + + @Override + protected Object getAuditable() { make } + + Make make = new Make(makeName: 'testMake', modelNameFormat: 'M T', segmentCodes: ['M', 'T']) + + def 'can create a valid Make'() { + when: + make.validate() + + then: + !make.hasErrors() + } + + @Unroll + def 'makeName with value #value validation error is #error'() { + given: + make.makeName = value + + when: + make.validate() + + then: + make.errors['makeName']?.code == error + + where: + value || error + null || 'nullable' + '' || 'blank' + 'A' || null + 'ok'.padRight(100, '_') || null + 'tooLong'.padRight(101, '_') || MAX_SIZE + } + + @Unroll + def 'modelNameFormat with value #value validation error is #error'() { + given: + make.modelNameFormat = value + + when: + make.validate() + + then: + make.errors['modelNameFormat']?.code == error + + where: + value || error + null || 'nullable' + '' || 'blank' + 'M'.padRight(50, '_') || null + 'M'.padRight(51, '_') || MAX_SIZE + } +} Index: src/test/groovy/com/lemans/ds/fitment/ModelSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/fitment/ModelSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/fitment/ModelSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,63 @@ +package com.lemans.ds.fitment + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor +import spock.lang.Unroll + +@TestFor(Model) +class ModelSpec extends Auditable32Spec { + + @Override + protected Object getAuditable() { model } + + Model model = new Model(make: new Make(id: 1, makeName: 'test', modelNameFormat: 'M'), modelName: 'testModel', + modelNameFormat: 'X - Y (Z)', vehicleTypeId: 1) + + def 'can create a valid Model'() { + when: + model.validate() + + then: + !model.hasErrors() + } + + @Unroll + def 'modelName with value #value validation error is #error'() { + given: + model.modelName = value + + when: + model.validate() + + then: + model.errors['modelName']?.code == error + + where: + value || error + null || null + '' || 'blank' + 'A' || null + 'ok'.padRight(400, '_') || null + 'tooLong'.padRight(401, '_') || MAX_SIZE + } + + @Unroll + def 'modelNameFormat with value #value validation error is #error'() { + given: + model.modelNameFormat = value + + when: + model.validate() + + then: + model.errors['modelNameFormat']?.code == error + + where: + value || error + null || null + '' || 'blank' + 'A' || null + 'ok'.padRight(50, '_') || null + 'tooLong'.padRight(51, '_') || MAX_SIZE + } +} Index: src/test/groovy/com/lemans/ds/fitment/ModelYearSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/fitment/ModelYearSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/fitment/ModelYearSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,55 @@ +package com.lemans.ds.fitment + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor +import spock.lang.Shared +import spock.lang.Unroll + +import java.time.Year + +@TestFor(ModelYear) +class ModelYearSpec extends Auditable32Spec { + + @Override + protected Object getAuditable() { modelYear } + + @Shared Integer currentYear = Year.now().value + + ModelYear modelYear = new ModelYear( + model: new Model( + make: new Make( + id: 1, makeName: 'test', modelNameFormat: 'X - Y' + ), modelName: 'testModel', modelNameFormat: 'X - Y (Z)' + ), + year: 2000) + + def 'can create a valid Year'() { + when: + modelYear.validate() + + then: + !modelYear.hasErrors() + } + + @Unroll + def 'year with value #value validation error is #error'() { + given: + modelYear.year = value + + when: + modelYear.validate() + + then: + modelYear.errors['year']?.code == error + + where: + value || error + null || 'nullable' + 2000 || null + currentYear || null + currentYear + 1 || null + currentYear + 2 || 'modelYear.year.rangeExceeded' + 1894 || null + 1893 || 'modelYear.year.rangeExceeded' + } +} Index: src/test/groovy/com/lemans/ds/part/PartAssociationSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/part/PartAssociationSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/part/PartAssociationSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,24 @@ +package com.lemans.ds.part + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor + +/** + * See the API for {@link grails.test.mixin.domain.DomainClassUnitTestMixin} for usage instructions + */ +@TestFor(PartAssociation) +class PartAssociationSpec extends Auditable32Spec { + + @Override + protected Object getAuditable() { partAssociation } + + PartAssociation partAssociation = new PartAssociation(partNumber: 'b8es', relatedPartNumber: 'b9es', associationTypeId: 20001) + + def 'can create a valid partAssociation'() { + when: + partAssociation.validate() + + then: + !partAssociation.hasErrors() + } +} Index: src/test/groovy/com/lemans/ds/part/PartSearchCommandSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/part/PartSearchCommandSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/part/PartSearchCommandSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,49 @@ +package com.lemans.ds.part + +import spock.lang.Shared +import spock.lang.Specification +import spock.lang.Unroll + +class PartSearchCommandSpec extends Specification { + + private static final String MIN_SIZE = 'minSize.notmet' + + PartSearchCommand cmd = new PartSearchCommand() + + @Shared + String tooShort = 'x' + + @Shared + String ok = 'ab' + + @Unroll + def 'PartSearchCommand with filter #filter #value validation error is #error'() { + given: + cmd[filter] = value + + when: + cmd.validate() + + then: + error == cmd.errors[filter]?.code + + where: + filter | value | error + 'q' | tooShort | MIN_SIZE + 'q' | ok | null + 'partNumber' | tooShort | MIN_SIZE + 'partNumber' | ok | null + 'partDescr' | tooShort | MIN_SIZE + 'partDescr' | ok | null + 'brandCode' | tooShort | MIN_SIZE + 'brandCode' | ok | null + 'brandName' | tooShort | MIN_SIZE + 'brandName' | ok | null + 'vendorId' | tooShort | MIN_SIZE + 'vendorId' | ok | null + 'vendorName' | tooShort | MIN_SIZE + 'vendorName' | ok | null + 'vendorPartNumber' | tooShort | MIN_SIZE + 'vendorPartNumber' | ok | null + } +} Index: src/test/groovy/com/lemans/ds/part/PartSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/part/PartSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/part/PartSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,47 @@ +package com.lemans.ds.part + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor +import spock.lang.Shared +import spock.lang.Unroll + +@TestFor(Part) +class PartSpec extends Auditable32Spec { + + @Override + protected getAuditable() { part } + + Part part = new Part(partNumber: 'B8ES') + + @Shared + String tooLong = 'This is long text of 102 characters, This is long text of 102 characters, so it is really a long text.' + + @Shared + String ok = 'Valid description' + + def 'can create a valid Part'() { + when: + auditable.validate() + + then: + !auditable.errors.hasErrors() + } + + @Unroll + def 'Part with filter #filter #value validation error is #error'() { + given: + part[filter] = value + + when: + part.validate() + + then: + error == part.errors[filter]?.code + + where: + filter | value | error + 'marketingDescr' | tooLong | MAX_SIZE + 'marketingDescr' | ok | null + } +} + Index: src/test/groovy/com/lemans/ds/product/ProductAssociationSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/product/ProductAssociationSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/product/ProductAssociationSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,24 @@ +package com.lemans.ds.product + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor + +/** + * See the API for {@link grails.test.mixin.domain.DomainClassUnitTestMixin} for usage instructions + */ +@TestFor(ProductAssociation) +class ProductAssociationSpec extends Auditable32Spec { + + @Override + protected Object getAuditable() { productAssociation } + + ProductAssociation productAssociation = new ProductAssociation(productId: 316571, relatedProductId: 316572, associationTypeId: 21001) + + def 'can create a valid productAssociation'() { + when: + productAssociation.validate() + + then: + !productAssociation.hasErrors() + } +} Index: src/test/groovy/com/lemans/ds/product/ProductFeatureSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/product/ProductFeatureSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/product/ProductFeatureSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,43 @@ +package com.lemans.ds.product + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor +import spock.lang.Unroll + +@TestFor(ProductFeature) +class ProductFeatureSpec extends Auditable32Spec { + + + @Override + protected Object getAuditable() { feature } + + ProductFeature feature = new ProductFeature(productId: 2, featureTypeId: 3, featureText: 'x' ) + + def 'can create a valid ProductFeature'() { + when: + feature.validate() + + then: + !feature.hasErrors() + } + + @Unroll + def 'featureText with value #value validation error is #error'() { + given: + feature.featureText = value + + when: + feature.validate() + + then: + feature.errors['featureText']?.code == error + + where: + value || error + null || 'nullable' + '' || 'blank' + 'A' || null + 'ok'.padRight(1500, '_') || null + 'tooLong'.padRight(1501, '_') || MAX_SIZE + } +} Index: src/test/groovy/com/lemans/ds/product/ProductSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/product/ProductSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/product/ProductSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,125 @@ +package com.lemans.ds.product + +import com.lemans.testing.Auditable32Spec + +import grails.test.mixin.TestFor +import spock.lang.Unroll + +@TestFor(Product) +class ProductSpec extends Auditable32Spec { + + private static final String MIN_SIZE = 'minSize.notmet' + + @Override + protected Object getAuditable() { product } + + Product product = new Product(catalogInstanceId: 1, productName: 'XYZ', isDigiActive: '1') + + def 'can create a valid Product'() { + when: + product.validate() + + then: + !product.hasErrors() + } + + @Unroll + def 'categoryId with value #categoryId and catalogInstanceId #catalogId validation error is #error'() { + given: + product.categoryId = categoryId + product.catalogInstanceId = catalogId + + when: + product.validate() + + then: + product.errors['categoryId']?.code == error + + where: + categoryId | catalogId || error + null | 1 || null + 2 | 1 || null + null | Product.VIRTUAL_CATALOG_ID || 'nullable' + 2 | Product.VIRTUAL_CATALOG_ID || null + } + + @Unroll + def 'brandId with value #brandId and catalogInstanceId #catalogId validation error is #error'() { + given: + product.brandId = brandId + product.catalogInstanceId = catalogId + + when: + product.validate() + + then: + product.errors['brandId']?.code == error + + where: + brandId | catalogId || error + null | 1 || null + 839 | 1 || null + null | Product.VIRTUAL_CATALOG_ID || 'nullable' + 839 | Product.VIRTUAL_CATALOG_ID || null + } + + @Unroll + def 'productName with value #value validation error is #error'() { + given: + product.productName = value + + when: + product.validate() + + then: + product.errors['productName']?.code == error + + where: + value || error + null || 'nullable' + 'AB' || MIN_SIZE + 'ABC' || null + 'ok'.padRight(150, '_') || null + 'tooLong'.padRight(151, '_') || MAX_SIZE + } + + @Unroll + def 'description with value #value validation error is #error'() { + given: + product.description = value + + when: + product.validate() + + then: + product.errors['description']?.code == error + + where: + value || error + null || null + '' || null + 'A' || null + 'ok'.padRight(2000, '_') || null + 'tooLong'.padRight(2001, '_') || MAX_SIZE + } + + @Unroll + def 'caption with value #value validation error is #error'() { + given: + product.caption = value + + when: + product.validate() + + then: + product.errors['caption']?.code == error + + where: + value || error + null || null + '' || null + 'A' || null + 'ok'.padRight(150, '_') || null + 'tooLong'.padRight(151, '_') || MAX_SIZE + } +} Index: src/test/groovy/com/lemans/ds/publicationcategory/ProductPublicationCategorySpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/publicationcategory/ProductPublicationCategorySpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/publicationcategory/ProductPublicationCategorySpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,21 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor + +@TestFor(ProductPublicationCategory) +class ProductPublicationCategorySpec extends Auditable32Spec { + + @Override + protected Object getAuditable() { productPublicationCategory } + + ProductPublicationCategory productPublicationCategory = new ProductPublicationCategory(productId: 316571 , categoryId: 15) + + def 'can create a valid partAssociation'() { + when: + productPublicationCategory.validate() + + then: + !productPublicationCategory.hasErrors() + } +} Index: src/test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryAttributeSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryAttributeSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/publicationcategory/PublicationCategoryAttributeSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,21 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor + +@TestFor(PublicationCategoryAttribute) +class PublicationCategoryAttributeSpec extends Auditable32Spec { + + @Override + protected Object getAuditable() { categoryAttribute } + + PublicationCategoryAttribute categoryAttribute = new PublicationCategoryAttribute(publicationCategoryId: 1, attributeNameId: 1) + + def 'can create a valid partAssociation'() { + when: + categoryAttribute.validate() + + then: + !categoryAttribute.hasErrors() + } +} Index: src/test/groovy/com/lemans/ds/publicationcategory/PublicationCategorySpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/publicationcategory/PublicationCategorySpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/publicationcategory/PublicationCategorySpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,21 @@ +package com.lemans.ds.publicationcategory + +import com.lemans.testing.Auditable32Spec +import grails.test.mixin.TestFor + +@TestFor(PublicationCategory) +class PublicationCategorySpec extends Auditable32Spec { + + @Override + protected Object getAuditable() { publicationCategory } + + PublicationCategory publicationCategory = new PublicationCategory(id: 15, categoryName: 'sheetalCategory', sequence: 1) + + def 'can create a valid partAssociation'() { + when: + publicationCategory.validate() + + then: + !publicationCategory.hasErrors() + } +} Index: src/test/groovy/com/lemans/ds/search/FilterXmlGeneratorSpec.groovy =================================================================== diff -u --- src/test/groovy/com/lemans/ds/search/FilterXmlGeneratorSpec.groovy (revision 0) +++ src/test/groovy/com/lemans/ds/search/FilterXmlGeneratorSpec.groovy (revision 3fb6c0e8e7068726c84a18eeb122d345e576b67f) @@ -0,0 +1,76 @@ +package com.lemans.ds.search + +import spock.lang.Specification + +/** + * Created by vramisetti on 2/17/2017. + */ +class FilterXmlGeneratorSpec extends Specification { + + + def 'can generate filter xml'() { + given: + Map input = [brandId: 123, vendorId: 456] + String filterType = 'PartsWithoutCategory' + String searchType = 'part' + + when: + String xml = new FilterXmlGenerator().generateXml(searchType, filterType, input) + + then: + xml == ''' + + 123 + 456 + +''' + } + + def 'can generate filter xml with filter options'() { + given: + Map input = [brandId: 123, vendorId: 456] + String filterType = 'part' + String searchType = 'part' + String filterName = 'productId' + + when: + String xml = new FilterXmlGenerator().generateXml(searchType, filterType, input + [filterName: filterName]) + + then: + xml == ''' + + 123 + 456 + + + productId + +''' + } + + def 'can NOT generate filter xml with invalid searchType'() { + given: + Map input = [partNumber: 'B8ES', q: 'test'] + String filterType = 'PartsWithoutCategory' + String searchType = '_invalid_' + + when: + String xml = new FilterXmlGenerator().generateXml(searchType, filterType, input) + + then: + xml == null + } + + def 'can NOT generate filter xml with invalid filterType'() { + given: + Map input = [partNumber: 'B8ES', q: 'test'] + String filterType = '_invalid_' + String searchType = 'part' + + when: + String xml = new FilterXmlGenerator().generateXml(searchType, filterType, input) + + then: + xml == null + } +}