| @@ -0,0 +1,111 @@ | |||||
| package com.ffii.core.utils; | |||||
| import java.util.HashMap; | |||||
| import java.util.Map; | |||||
| /** | |||||
| * File Utils | |||||
| * | |||||
| * @author Patrick | |||||
| */ | |||||
| public abstract class FileUtils { | |||||
| private static final Map<String, String> MIMETYPES = new HashMap<>(); | |||||
| static { | |||||
| MIMETYPES.put("pdf", "application/pdf"); | |||||
| MIMETYPES.put("doc", "application/msword"); | |||||
| MIMETYPES.put("dot", "application/msword"); | |||||
| MIMETYPES.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); | |||||
| MIMETYPES.put("xls", "application/vnd.ms-excel"); | |||||
| MIMETYPES.put("xlm", "application/vnd.ms-excel"); | |||||
| MIMETYPES.put("xla", "application/vnd.ms-excel"); | |||||
| MIMETYPES.put("xlc", "application/vnd.ms-excel"); | |||||
| MIMETYPES.put("xlt", "application/vnd.ms-excel"); | |||||
| MIMETYPES.put("xlw", "application/vnd.ms-excel"); | |||||
| MIMETYPES.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); | |||||
| MIMETYPES.put("ppt", "application/vnd.ms-powerpoint"); | |||||
| MIMETYPES.put("pps", "application/vnd.ms-powerpoint"); | |||||
| MIMETYPES.put("pot", "application/vnd.ms-powerpoint"); | |||||
| MIMETYPES.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"); | |||||
| MIMETYPES.put("bat", "application/x-msdownload"); | |||||
| MIMETYPES.put("com", "application/x-msdownload"); | |||||
| MIMETYPES.put("dll", "application/x-msdownload"); | |||||
| MIMETYPES.put("exe", "application/x-msdownload"); | |||||
| MIMETYPES.put("msi", "application/x-msdownload"); | |||||
| MIMETYPES.put("swf", "application/x-shockwave-flash"); | |||||
| MIMETYPES.put("7z", "application/x-7z-compressed"); | |||||
| MIMETYPES.put("rar", "application/x-rar-compressed"); | |||||
| MIMETYPES.put("zip", "application/zip"); | |||||
| MIMETYPES.put("js", "application/javascript"); | |||||
| MIMETYPES.put("json", "application/json"); | |||||
| MIMETYPES.put("mpga", "audio/mpeg"); | |||||
| MIMETYPES.put("mp2", "audio/mpeg"); | |||||
| MIMETYPES.put("mp2a", "audio/mpeg"); | |||||
| MIMETYPES.put("mp3", "audio/mpeg"); | |||||
| MIMETYPES.put("m2a", "audio/mpeg"); | |||||
| MIMETYPES.put("m3a", "audio/mpeg"); | |||||
| MIMETYPES.put("bmp", "image/bmp"); | |||||
| MIMETYPES.put("gif", "image/gif"); | |||||
| MIMETYPES.put("jpeg", "image/jpeg"); | |||||
| MIMETYPES.put("jpg", "image/jpeg"); | |||||
| MIMETYPES.put("jpe", "image/jpeg"); | |||||
| MIMETYPES.put("png", "image/png"); | |||||
| MIMETYPES.put("tiff", "image/tiff"); | |||||
| MIMETYPES.put("tif", "image/tiff"); | |||||
| MIMETYPES.put("css", "text/css"); | |||||
| MIMETYPES.put("csv", "text/csv"); | |||||
| MIMETYPES.put("html", "text/html"); | |||||
| MIMETYPES.put("htm", "text/html"); | |||||
| MIMETYPES.put("txt", "text/plain"); | |||||
| MIMETYPES.put("text", "text/plain"); | |||||
| MIMETYPES.put("conf", "text/plain"); | |||||
| MIMETYPES.put("log", "text/plain"); | |||||
| MIMETYPES.put("mp4", "video/mp4"); | |||||
| MIMETYPES.put("mp4v", "video/mp4"); | |||||
| MIMETYPES.put("mpg4", "video/mp4"); | |||||
| MIMETYPES.put("mpeg", "video/mpeg"); | |||||
| MIMETYPES.put("mpg", "video/mpeg"); | |||||
| MIMETYPES.put("mpe", "video/mpeg"); | |||||
| MIMETYPES.put("m1v", "video/mpeg"); | |||||
| MIMETYPES.put("m2v", "video/mpeg"); | |||||
| MIMETYPES.put("qt", "video/quicktime"); | |||||
| MIMETYPES.put("mov", "video/quicktime"); | |||||
| MIMETYPES.put("wmv", "video/x-ms-wmv"); | |||||
| MIMETYPES.put("wmx", "video/x-ms-wmx"); | |||||
| MIMETYPES.put("wvx", "video/x-ms-wvx"); | |||||
| MIMETYPES.put("avi", "video/x-msvideo"); | |||||
| // MIMETYPES.put("xxxxx", "xxxxx"); | |||||
| } | |||||
| /** | |||||
| * Guess the mimetype from the file name extension | |||||
| * | |||||
| * @return The mimetype guessed from the file name extension, or {@code null} if the mimetype cannot be determined | |||||
| */ | |||||
| public static String guessMimetype(String filename) { | |||||
| String extension = StringUtils.substringAfterLast(filename, "."); | |||||
| String mimetype = MIMETYPES.get(extension); | |||||
| return mimetype != null ? mimetype : "application/octet-stream"; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,461 @@ | |||||
| package com.ffii.core.utils; | |||||
| import java.math.BigDecimal; | |||||
| import java.math.RoundingMode; | |||||
| import org.apache.commons.lang3.ArrayUtils; | |||||
| /** | |||||
| * NumberUtils extends from Apache Commons, and incl some methods from MathUtils | |||||
| * | |||||
| * @author Patrick | |||||
| */ | |||||
| public abstract class NumberUtils extends org.apache.commons.lang3.math.NumberUtils { | |||||
| /** | |||||
| * Round the given value to the specified number of decimal places. The value is rounded using the given method which is any method defined in | |||||
| * {@link BigDecimal}. | |||||
| * | |||||
| * @param x | |||||
| * the value to round | |||||
| * @param scale | |||||
| * the number of digits to the right of the decimal point | |||||
| * @param roundingMethod | |||||
| * the rounding method as defined in {@link RoundingMode} | |||||
| * @return the rounded value | |||||
| */ | |||||
| public static double round(double x, int scale, RoundingMode roundingMethod) { | |||||
| try { | |||||
| return BigDecimal.valueOf(x).setScale(scale, roundingMethod).doubleValue(); | |||||
| } catch (NumberFormatException ex) { | |||||
| if (Double.isInfinite(x)) { | |||||
| return x; | |||||
| } else { | |||||
| return Double.NaN; | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Round the given value to the specified number of decimal places. The value is rounded using the {@link RoundingMode#HALF_UP} method. | |||||
| * | |||||
| * @param x | |||||
| * the value to round | |||||
| * @param scale | |||||
| * the number of digits to the right of the decimal point | |||||
| * @return the rounded value | |||||
| * @see org.apache.commons.math.util.MathUtils#round(double, int) | |||||
| */ | |||||
| public static double round(double x, int scale) { | |||||
| return round(x, scale, RoundingMode.HALF_UP); | |||||
| } | |||||
| /** | |||||
| * Round up the given value to the specified number of decimal places. The value is rounded up using the {@link RoundingMode#UP} method. | |||||
| * | |||||
| * @param x | |||||
| * the value to round up | |||||
| * @param scale | |||||
| * the number of digits to the right of the decimal point | |||||
| * @return the rounded up value | |||||
| */ | |||||
| public static double roundUp(double x, int scale) { | |||||
| return round(x, scale, RoundingMode.UP); | |||||
| } | |||||
| /** | |||||
| * Round down the given value to the specified number of decimal places. The value is rounded down using the {@link RoundingMode#DOWN} method. | |||||
| * | |||||
| * @param x | |||||
| * the value to round down | |||||
| * @param scale | |||||
| * the number of digits to the right of the decimal point | |||||
| * @return the rounded down value | |||||
| */ | |||||
| public static double roundDown(double x, int scale) { | |||||
| return round(x, scale, RoundingMode.DOWN); | |||||
| } | |||||
| /** | |||||
| * Return the {@code int} value of an {@code Object}, or the default value if the object is either {@code null} or not an instance of {@code Number}. | |||||
| * | |||||
| * @param obj | |||||
| * the {@code Object} | |||||
| * @param defaultValue | |||||
| * the default value | |||||
| * @return the {@code int} value of the {@code Object}, or the default if the object is either {@code null} or not an instance of {@code Number} | |||||
| * @see Number#intValue() | |||||
| */ | |||||
| public static int intValue(Object obj, int defaultValue) { | |||||
| if (obj instanceof Number) { | |||||
| return ((Number) obj).intValue(); | |||||
| } else if (obj instanceof String) { | |||||
| try { | |||||
| return Integer.parseInt((String) obj); | |||||
| } catch (NumberFormatException nfe) { | |||||
| } | |||||
| } | |||||
| return defaultValue; | |||||
| } | |||||
| /** | |||||
| * Return the {@code int} value of an {@code Object}, or {@code zero} if the object is either {@code null} or not an instance of {@code Number}. | |||||
| * | |||||
| * @param obj | |||||
| * the {@code Object} | |||||
| * @return the {@code int} value of the {@code Object}, or {@code zero} if the object is either {@code null} or not an instance of {@code Number} | |||||
| * @see Number#intValue() | |||||
| */ | |||||
| public static int intValue(Object obj) { | |||||
| return intValue(obj, 0); | |||||
| } | |||||
| /** | |||||
| * Convert an {@code Object} to an {@code Integer} (only if the object is an instance of {@code Number} or {@code String}), returning a default value if the | |||||
| * conversion fails. | |||||
| * <p> | |||||
| * If the object is {@code null}, the default value is returned. | |||||
| * | |||||
| * @param obj | |||||
| * the object to convert, may be {@code null} | |||||
| * @param defaultValue | |||||
| * the default value, may be {@code null} | |||||
| * @return the Integer represented by the object, or the default if conversion fails | |||||
| */ | |||||
| public static Integer toInt(Object obj, Integer defaultValue) { | |||||
| if (obj instanceof Number) | |||||
| return Integer.valueOf(((Number) obj).intValue()); | |||||
| else if (obj instanceof String) | |||||
| try { | |||||
| return Integer.valueOf((String) obj); | |||||
| } catch (NumberFormatException nfe) { | |||||
| return defaultValue; | |||||
| } | |||||
| else | |||||
| return defaultValue; | |||||
| } | |||||
| /** | |||||
| * Return the {@code long} value of a {@code Long} object, or a default value if the object is {@code null}. | |||||
| * | |||||
| * @param obj | |||||
| * the object (can be {@code null}) | |||||
| * @param defaultValue | |||||
| * the default value | |||||
| * @return the {@code long} value of the object if it's a number, or the default value if the object is {@code null} or {@code NaN} | |||||
| * @see Long#longValue() | |||||
| */ | |||||
| public static long longValue(Object obj, long defaultValue) { | |||||
| return (obj instanceof Number) ? ((Number) obj).longValue() : defaultValue; | |||||
| } | |||||
| /** | |||||
| * Return the {@code long} value of a {@code Long} object, or {@code zero} if the object is {@code null}. | |||||
| * | |||||
| * @param obj | |||||
| * the object (can be {@code null}) | |||||
| * @return the {@code long} value of the object if it's a number, or {@code zero} if the object is {@code null} or {@code NaN} | |||||
| * @see Long#longValue() | |||||
| */ | |||||
| public static long longValue(Object obj) { | |||||
| return longValue(obj, 0l); | |||||
| } | |||||
| /** | |||||
| * Convert an {@code Object} to a {@code Long} (only if the object is an instance of {@code Number} or {@code String}), returning a default value if the | |||||
| * conversion fails. | |||||
| * <p> | |||||
| * If the object is {@code null}, the default value is returned. | |||||
| * | |||||
| * @param obj | |||||
| * the object to convert, may be {@code null} | |||||
| * @param defaultValue | |||||
| * the default value, may be {@code null} | |||||
| * @return the Long represented by the object, or the default if conversion fails | |||||
| */ | |||||
| public static Long toLong(Object obj, Long defaultValue) { | |||||
| if (obj instanceof Number) | |||||
| return Long.valueOf(((Number) obj).longValue()); | |||||
| else if (obj instanceof String) | |||||
| try { | |||||
| return Long.valueOf((String) obj); | |||||
| } catch (NumberFormatException nfe) { | |||||
| return defaultValue; | |||||
| } | |||||
| else | |||||
| return defaultValue; | |||||
| } | |||||
| /** | |||||
| * @param obj | |||||
| * the object (can be {@code null}) | |||||
| * @param defaultValue | |||||
| * the default value | |||||
| * @return the {@code double} value of the object if it's a number, or the default value if the object is {@code null} or {@code NaN} | |||||
| * @see Double#doubleValue() | |||||
| */ | |||||
| public static double doubleValue(Object obj, double defaultValue) { | |||||
| return (obj instanceof Number) ? ((Number) obj).doubleValue() : defaultValue; | |||||
| } | |||||
| /** | |||||
| * @param obj | |||||
| * the object (can be {@code null}) | |||||
| * @param defaultValue | |||||
| * the default value | |||||
| * @return the {@code double} value of the object if it's a number, or {@code zero} if the object is {@code null} or {@code NaN} | |||||
| * @see Double#doubleValue() | |||||
| */ | |||||
| public static double doubleValue(Object obj) { | |||||
| return doubleValue(obj, 0.0d); | |||||
| } | |||||
| /** | |||||
| * Convert an {@code Object} to a {@code Double} (only if the object is an instance of {@code Number} or {@code String}), returning a default value if the | |||||
| * conversion fails. | |||||
| * <p> | |||||
| * If the object is {@code null}, the default value is returned. | |||||
| * | |||||
| * @param obj | |||||
| * the object to convert, may be {@code null} | |||||
| * @param defaultValue | |||||
| * the default value, may be {@code null} | |||||
| * @return the Double represented by the object, or the default if conversion fails | |||||
| */ | |||||
| public static Double toDouble(Object obj, Double defaultValue) { | |||||
| if (obj instanceof Number) | |||||
| return Double.valueOf(((Number) obj).doubleValue()); | |||||
| else if (obj instanceof String) | |||||
| try { | |||||
| return Double.valueOf((String) obj); | |||||
| } catch (NumberFormatException nfe) { | |||||
| return defaultValue; | |||||
| } | |||||
| else | |||||
| return defaultValue; | |||||
| } | |||||
| /** | |||||
| * Return the {@code BigDecimal} object, or {@code zero} if the object is {@code null}. | |||||
| * | |||||
| * @param obj | |||||
| * the {@code BigDecimal} object | |||||
| * @return the {@code BigDecimal} object, or {@code zero} if the object is {@code null} | |||||
| */ | |||||
| public static BigDecimal decimalValue(BigDecimal obj) { | |||||
| return decimalValue(obj, BigDecimal.ZERO); | |||||
| } | |||||
| /** | |||||
| * Return the {@code BigDecimal} object, or a default value if the object is {@code null}. | |||||
| * | |||||
| * @param obj | |||||
| * the {@code BigDecimal} object | |||||
| * @param defaultValue | |||||
| * the default value | |||||
| * @return the {@code BigDecimal} object, or the default if the object is {@code null} | |||||
| */ | |||||
| public static BigDecimal decimalValue(BigDecimal obj, BigDecimal defaultValue) { | |||||
| return obj == null ? defaultValue : obj; | |||||
| } | |||||
| /** | |||||
| * Convert an {@code Object} to a {@code BigDecimal}, returning {@code BigDecimal.ZERO} if the conversion fails (e.g. the object is not an instance of | |||||
| * {@code Number} nor {@code String}). | |||||
| * <p> | |||||
| * If the object is {@code null}, {@code BigDecimal.ZERO} is returned. | |||||
| * | |||||
| * @param obj | |||||
| * the object to convert, may be {@code null} | |||||
| * @return the BigDecimal represented by the object, or {@code BigDecimal.ZERO} if conversion fails | |||||
| */ | |||||
| public static BigDecimal toDecimal(Object obj) { | |||||
| return toDecimal(obj, BigDecimal.ZERO); | |||||
| } | |||||
| /** | |||||
| * Convert an {@code Object} to a {@code BigDecimal}, returning a default value if the conversion fails (e.g. the object is not an instance of | |||||
| * {@code Number} nor {@code String}). | |||||
| * <p> | |||||
| * If the object is {@code null}, the default value is returned. | |||||
| * | |||||
| * @param obj | |||||
| * the object to convert, may be {@code null} | |||||
| * @param defaultValue | |||||
| * the default value, may be {@code null} | |||||
| * @return the BigDecimal represented by the object, or the default if conversion fails | |||||
| */ | |||||
| public static BigDecimal toDecimal(Object obj, BigDecimal defaultValue) { | |||||
| if (obj instanceof BigDecimal) | |||||
| return (BigDecimal) obj; | |||||
| else if (obj instanceof Number) | |||||
| return BigDecimal.valueOf(((Number) obj).doubleValue()); | |||||
| else if (obj instanceof String) | |||||
| try { | |||||
| return new BigDecimal((String) obj); | |||||
| } catch (NumberFormatException nfe) { | |||||
| return defaultValue; | |||||
| } | |||||
| else | |||||
| return defaultValue; | |||||
| } | |||||
| /** | |||||
| * Null-safe method to check if the two {@code Integer} objects have the same value. | |||||
| * | |||||
| * <ol> | |||||
| * <li>Returns {@code true} if {@code a} and {@code b} are both {@code null}. | |||||
| * <li>Returns {@code false} if only one of them is {@code null}. | |||||
| * <li>Returns {@code true} if {@code a} and {@code b} are not {@code null} and have the same {@code int} value, else returns {@code false}. | |||||
| * </ol> | |||||
| * | |||||
| * @param a | |||||
| * Integer obj, may be {@code null} | |||||
| * @param b | |||||
| * Integer obj, may be {@code null} | |||||
| */ | |||||
| public static boolean isEqual(Integer a, Integer b) { | |||||
| return a == null ? (b == null ? true : false) : (b == null ? false : a.equals(b)); | |||||
| } | |||||
| /** | |||||
| * Null-safe method to check if the two {@code Integer} objects have different values. | |||||
| * | |||||
| * <ol> | |||||
| * <li>Returns {@code false} if {@code a} and {@code b} are both {@code null}. | |||||
| * <li>Returns {@code true} if only one of them is {@code null}. | |||||
| * <li>Returns {@code true} if {@code a} and {@code b} are not {@code null} and have different {@code int} values, else returns {@code false}. | |||||
| * </ol> | |||||
| * | |||||
| * @param a | |||||
| * Integer obj, may be {@code null} | |||||
| * @param b | |||||
| * Integer obj, may be {@code null} | |||||
| */ | |||||
| public static boolean isNotEqual(Integer a, Integer b) { | |||||
| return !isEqual(a, b); | |||||
| } | |||||
| /** | |||||
| * Null-safe method to check if the two {@code BigDecimal} objects have the same value. | |||||
| * <p> | |||||
| * Two {@code BigDecimal} objects that are equal in value but have a different scale (like 2.0 and 2.00) are considered equal by this method. | |||||
| * | |||||
| * <ol> | |||||
| * <li>Returns {@code true} if {@code a} and {@code b} are both {@code null}. | |||||
| * <li>Returns {@code false} if only one of them is {@code null}. | |||||
| * <li>Returns {@code true} if {@code a} and {@code b} are not {@code null} and have the same {@code decimal} value, else returns {@code false}. | |||||
| * </ol> | |||||
| * | |||||
| * @param a | |||||
| * BigDecimal obj, may be {@code null} | |||||
| * @param b | |||||
| * BigDecimal obj, may be {@code null} | |||||
| */ | |||||
| public static boolean isEqual(BigDecimal a, BigDecimal b) { | |||||
| return a == null ? (b == null ? true : false) : (b == null ? false : a.compareTo(b) == 0); | |||||
| } | |||||
| /** | |||||
| * Null-safe method to check if the two {@code BigDecimal} objects have different values. | |||||
| * <p> | |||||
| * Two {@code BigDecimal} objects that are equal in value but have a different scale (like 2.0 and 2.00) are considered equal by this method. | |||||
| * | |||||
| * <ol> | |||||
| * <li>Returns {@code false} if {@code a} and {@code b} are both {@code null}. | |||||
| * <li>Returns {@code true} if only one of them is {@code null}. | |||||
| * <li>Returns {@code true} if {@code a} and {@code b} are not {@code null} and have different {@code decimal} values, else returns {@code false}. | |||||
| * </ol> | |||||
| * | |||||
| * @param a | |||||
| * BigDecimal obj, may be {@code null} | |||||
| * @param b | |||||
| * BigDecimal obj, may be {@code null} | |||||
| */ | |||||
| public static boolean isNotEqual(BigDecimal a, BigDecimal b) { | |||||
| return !isEqual(a, b); | |||||
| } | |||||
| /** | |||||
| * Check if {@code BigDecimal} object {@code a} is greater than {@code BigDecimal} object {@code b}. | |||||
| * | |||||
| * @param a | |||||
| * non-{@code null} BigDecimal obj | |||||
| * @param b | |||||
| * non-{@code null} BigDecimal obj | |||||
| */ | |||||
| public static boolean isGreaterThan(BigDecimal a, BigDecimal b) { | |||||
| return a.compareTo(b) > 0; | |||||
| } | |||||
| /** | |||||
| * Check if {@code BigDecimal} object {@code a} is greater than or equals to {@code BigDecimal} object {@code b}. | |||||
| * | |||||
| * @param a | |||||
| * non-{@code null} BigDecimal obj | |||||
| * @param b | |||||
| * non-{@code null} BigDecimal obj | |||||
| */ | |||||
| public static boolean isGreaterThanOrEqual(BigDecimal a, BigDecimal b) { | |||||
| return a.compareTo(b) >= 0; | |||||
| } | |||||
| /** | |||||
| * Check if {@code BigDecimal} object {@code a} is less than {@code BigDecimal} object {@code b}. | |||||
| * | |||||
| * @param a | |||||
| * non-{@code null} BigDecimal obj | |||||
| * @param b | |||||
| * non-{@code null} BigDecimal obj | |||||
| */ | |||||
| public static boolean isLessThan(BigDecimal a, BigDecimal b) { | |||||
| return a.compareTo(b) < 0; | |||||
| } | |||||
| /** | |||||
| * Check if {@code BigDecimal} object {@code a} is less than or equals to {@code BigDecimal} object {@code b}. | |||||
| * | |||||
| * @param a | |||||
| * non-{@code null} BigDecimal obj | |||||
| * @param b | |||||
| * non-{@code null} BigDecimal obj | |||||
| */ | |||||
| public static boolean isLessThanOrEqual(BigDecimal a, BigDecimal b) { | |||||
| return a.compareTo(b) <= 0; | |||||
| } | |||||
| /** | |||||
| * | |||||
| * <pre> | |||||
| * NumberUtils.equalsAny(null, (Integer[]) null) = false | |||||
| * NumberUtils.equalsAny(null, null, null) = true | |||||
| * NumberUtils.equalsAny(null, 1, 2) = false | |||||
| * NumberUtils.equalsAny(1, null, 2) = false | |||||
| * NumberUtils.equalsAny(1, 1, 2) = true | |||||
| * </pre> | |||||
| * | |||||
| * @param int | |||||
| * to compare, may be {@code null}. | |||||
| * @param searchInts | |||||
| * a int, may be {@code null}. | |||||
| * @return {@code true} if the num is equal to any other element of <code>searchInts</code>; {@code false} if <code>searchInts</code> is null or contains no | |||||
| * matches. | |||||
| */ | |||||
| public static boolean equalsAny(final int num, int... searchInts) { | |||||
| if (ArrayUtils.isNotEmpty(searchInts)) { | |||||
| for (int next : searchInts) { | |||||
| if (num == next) { | |||||
| return true; | |||||
| } | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| public static double sum(double... nums) { | |||||
| BigDecimal rs = BigDecimal.ZERO; | |||||
| for (double num : nums) | |||||
| rs = rs.add(BigDecimal.valueOf(num)); | |||||
| return rs.doubleValue(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,73 @@ | |||||
| package com.ffii.core.utils; | |||||
| /** | |||||
| * String Utils based on Apache Commons StringUtils. | |||||
| * | |||||
| * @author Patrick | |||||
| */ | |||||
| public abstract class StringUtils extends org.apache.commons.lang3.StringUtils { | |||||
| /** | |||||
| * The String {@code "0"}. | |||||
| */ | |||||
| public static final String ZERO = "0"; | |||||
| /** | |||||
| * The String {@code "1"}. | |||||
| */ | |||||
| public static final String ONE = "1"; | |||||
| /** | |||||
| * The String {@code "%"}. | |||||
| */ | |||||
| public static final String PERCENT = "%"; | |||||
| /** | |||||
| * The String {@code ","}. | |||||
| */ | |||||
| public static final String COMMA = ","; | |||||
| /** | |||||
| * The String {@code "\r\n"} for line break on Windows | |||||
| */ | |||||
| public static final String LINE_BREAK_WINDOWS = "\r\n"; | |||||
| /** | |||||
| * The String {@code "\n"} for line break on Unix/Linux | |||||
| */ | |||||
| public static final String LINE_BREAK_LINUX = "\n"; | |||||
| public static final String[] A2Z_LOWWER = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", | |||||
| "o", "p", "q", "r", "s", "t", "u", "v", | |||||
| "w", "x", "y", "z" }; | |||||
| public static final String[] A2Z_UPPER = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", | |||||
| "O", "P", "Q", "R", "S", "T", "U", "V", | |||||
| "W", "X", "Y", "Z" }; | |||||
| public static final String concat(String segregator, final String... chars) { | |||||
| if (segregator == null) | |||||
| segregator = ""; | |||||
| String rs = ""; | |||||
| for (String c : chars) { | |||||
| if (c == null) | |||||
| continue; | |||||
| else { | |||||
| if (StringUtils.isBlank(rs)) { | |||||
| rs = c; | |||||
| } else { | |||||
| rs += segregator + c; | |||||
| } | |||||
| } | |||||
| } | |||||
| return rs; | |||||
| } | |||||
| public static final String removeLineBreak(String str) { | |||||
| if (str == null) | |||||
| return str; | |||||
| return str.replace("\r\n", " ") | |||||
| .replace("\n", " ") | |||||
| .replace("\r", " ") | |||||
| .trim(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,29 @@ | |||||
| package com.ffii.core.view; | |||||
| public abstract class AbstractView extends org.springframework.web.servlet.view.AbstractView { | |||||
| public static final String CONTENT_TYPE_JSON = "application/json"; | |||||
| public static final String CONTENT_TYPE_JAVASCRIPT = "text/javascript"; | |||||
| public static final String CONTENT_TYPE_TEXT_HTML = "text/html"; | |||||
| public static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain"; | |||||
| public static final String CONTENT_TYPE_XML = "text/xml"; | |||||
| public static final String CONTENT_TYPE_JPEG = "image/jpeg"; | |||||
| public static final String CONTENT_TYPE_PNG = "image/png"; | |||||
| public static final String CHARSET_UTF8 = "UTF-8"; | |||||
| protected boolean disableCaching = true; | |||||
| /** | |||||
| * Disables caching of the generated JSON. <br> | |||||
| * Default is {@code true}, which will prevent the client from caching the generated JSON. | |||||
| */ | |||||
| public void setDisableCaching(boolean disableCaching) { | |||||
| this.disableCaching = disableCaching; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,46 @@ | |||||
| package com.ffii.tsms.modules.claim.entity | |||||
| import com.ffii.core.entity.BaseEntity | |||||
| import com.ffii.core.entity.IdEntity | |||||
| import com.ffii.tsms.modules.data.entity.Staff | |||||
| import com.ffii.tsms.modules.file.entity.File | |||||
| import com.ffii.tsms.modules.project.entity.Project | |||||
| import jakarta.persistence.Column | |||||
| import jakarta.persistence.Entity | |||||
| import jakarta.persistence.JoinColumn | |||||
| import jakarta.persistence.ManyToOne | |||||
| import jakarta.persistence.OneToOne | |||||
| import jakarta.persistence.Table | |||||
| import jakarta.validation.constraints.NotNull | |||||
| import java.math.BigDecimal | |||||
| import java.time.LocalDateTime | |||||
| @Entity | |||||
| @Table(name = "claim") | |||||
| open class Claim : BaseEntity<Long>() { | |||||
| @NotNull | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "staffId") | |||||
| open var staff: Staff? = null | |||||
| @Column(name = "code") | |||||
| open var code: String? = null | |||||
| @NotNull | |||||
| @Column(name = "type") | |||||
| open var type: String? = null | |||||
| @NotNull | |||||
| @Column(name = "status") | |||||
| open var status: String? = null | |||||
| @Column(name = "verifiedDatetime") | |||||
| open var verifiedDatetime: LocalDateTime? = null | |||||
| @Column(name = "verifiedBy") | |||||
| open var verifiedBy: Long? = null | |||||
| @Column(name = "remark", length = 255) | |||||
| open var remark: String? = null | |||||
| } | |||||
| @@ -0,0 +1,47 @@ | |||||
| package com.ffii.tsms.modules.claim.entity | |||||
| import com.ffii.core.entity.BaseEntity | |||||
| import com.ffii.tsms.modules.data.entity.Staff | |||||
| import com.ffii.tsms.modules.file.entity.File | |||||
| import com.ffii.tsms.modules.project.entity.Project | |||||
| import jakarta.persistence.Column | |||||
| import jakarta.persistence.Entity | |||||
| import jakarta.persistence.JoinColumn | |||||
| import jakarta.persistence.ManyToOne | |||||
| import jakarta.persistence.OneToOne | |||||
| import jakarta.persistence.Table | |||||
| import jakarta.validation.constraints.NotNull | |||||
| import java.math.BigDecimal | |||||
| import java.time.LocalDate | |||||
| import java.time.LocalDateTime | |||||
| @Entity | |||||
| @Table(name = "claim_detail") | |||||
| open class ClaimDetail : BaseEntity<Long>() { | |||||
| @NotNull | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "claimId") | |||||
| open var claim: Claim? = null | |||||
| @NotNull | |||||
| @Column(name = "invoiceDate") | |||||
| open var invoiceDate: LocalDate? = null | |||||
| @NotNull | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "projectId") | |||||
| open var project: Project? = null | |||||
| @NotNull | |||||
| @Column(name = "description", length = 255) | |||||
| open var description: String? = null | |||||
| @NotNull | |||||
| @Column(name = "amount") | |||||
| open var amount: BigDecimal? = null | |||||
| @OneToOne | |||||
| @JoinColumn(name = "fileId") | |||||
| open var file: File? = null | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| package com.ffii.tsms.modules.claim.entity | |||||
| import com.ffii.core.support.AbstractRepository | |||||
| interface ClaimDetailRepository : AbstractRepository<ClaimDetail, Long> { | |||||
| fun findAllByDeletedFalse(): List<ClaimDetail> | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| package com.ffii.tsms.modules.claim.entity | |||||
| import com.ffii.core.support.AbstractRepository | |||||
| import org.springframework.data.repository.query.Param | |||||
| interface ClaimRepository : AbstractRepository<Claim, Long> { | |||||
| fun findAllByDeletedFalse(): List<Claim> | |||||
| fun findAllByCodeContains(@Param("code") code:String): List<Claim> | |||||
| } | |||||
| @@ -0,0 +1,92 @@ | |||||
| package com.ffii.tsms.modules.claim.service | |||||
| import com.ffii.tsms.modules.claim.entity.Claim | |||||
| import com.ffii.tsms.modules.claim.entity.ClaimDetail | |||||
| import com.ffii.tsms.modules.claim.entity.ClaimDetailRepository | |||||
| import com.ffii.tsms.modules.claim.entity.ClaimRepository | |||||
| import com.ffii.tsms.modules.claim.web.models.SaveClaimDetailRequest | |||||
| import com.ffii.tsms.modules.claim.web.models.SaveClaimRequest | |||||
| import com.ffii.tsms.modules.claim.web.models.SaveClaimResponse | |||||
| import com.ffii.tsms.modules.file.entity.FileBlob | |||||
| import com.ffii.tsms.modules.file.service.FileService | |||||
| import com.ffii.tsms.modules.project.entity.ProjectRepository | |||||
| import org.springframework.beans.BeanUtils | |||||
| import org.springframework.stereotype.Service | |||||
| import org.springframework.transaction.annotation.Transactional | |||||
| import java.text.NumberFormat | |||||
| import java.time.LocalDate | |||||
| import java.time.format.DateTimeFormatter | |||||
| import java.util.HashMap | |||||
| @Service | |||||
| open class ClaimService( | |||||
| private val claimRepository: ClaimRepository, | |||||
| private val fileService: FileService, private val claimDetailRepository: ClaimDetailRepository, private val projectRepository: ProjectRepository | |||||
| ) { | |||||
| open fun allClaims(): List<Claim> { | |||||
| return claimRepository.findAllByDeletedFalse() | |||||
| } | |||||
| open fun allClaimsByCodeContains(code: String): List<Claim> { | |||||
| return claimRepository.findAllByCodeContains(code) | |||||
| } | |||||
| open fun generateCode(): String { | |||||
| val dateFormatter = DateTimeFormatter.ofPattern("yyMMdd") | |||||
| val formattedToday = LocalDate.now().format(dateFormatter) | |||||
| val claims = allClaimsByCodeContains(formattedToday) | |||||
| // Format: CF-yymmddXXX | |||||
| return "CF-" + formattedToday + String.format("%03d", (claims.size + 1)) | |||||
| } | |||||
| @Transactional(rollbackFor = [Exception::class]) | |||||
| open fun saveClaimDetail(claimDetail: SaveClaimDetailRequest, claim: Claim) { | |||||
| val oldSupportingDocumentId = claimDetail.oldSupportingDocument?.id // fileId | |||||
| val newSupportingDocument = claimDetail.newSupportingDocument | |||||
| val claimDetailId = claimDetail.id | |||||
| val projectId = claimDetail.project.id | |||||
| var result: MutableMap<String, Any> = HashMap<String, Any>() | |||||
| if (oldSupportingDocumentId != null && oldSupportingDocumentId > 0 && newSupportingDocument != null) { | |||||
| result = fileService.updateFile(multipartFile = newSupportingDocument, refId = claim.id, refType = "claimDetail", refCode = claim.code, maxSize = 50, fileId = oldSupportingDocumentId) | |||||
| } else if ((oldSupportingDocumentId == null || oldSupportingDocumentId <= 0) && newSupportingDocument != null) { | |||||
| result = fileService.uploadFile(multipartFile = newSupportingDocument, refId = claim.id, refType = "claimDetail", refCode = claim.code, maxSize = 50) | |||||
| } | |||||
| val instance = claimDetailRepository.findById(claimDetailId).orElse(ClaimDetail()) | |||||
| BeanUtils.copyProperties(claimDetail, instance) | |||||
| val project = projectRepository.findById(projectId!!).orElseThrow() | |||||
| instance.apply { | |||||
| this.project = project | |||||
| } | |||||
| claimDetailRepository.save(instance) | |||||
| } | |||||
| @Transactional(rollbackFor = [Exception::class]) | |||||
| open fun saveClaim(saveClaimRequest: SaveClaimRequest): SaveClaimResponse { | |||||
| val claimId = saveClaimRequest.id | |||||
| val addClaimDetails = saveClaimRequest.addClaimDetails | |||||
| // Save to claim | |||||
| val instance = if (claimId != null && claimId > 0) claimRepository.findById(claimId).orElseThrow() else Claim(); | |||||
| instance.apply { | |||||
| type = saveClaimRequest.expenseType | |||||
| code = if(instance.code.isNullOrEmpty()) generateCode() else instance.code | |||||
| } | |||||
| claimRepository.save(instance) | |||||
| // Save to claim detail | |||||
| if (addClaimDetails.isNotEmpty()) { | |||||
| for(addClaimDetail in addClaimDetails) { | |||||
| saveClaimDetail(addClaimDetail, instance) | |||||
| } | |||||
| } | |||||
| return SaveClaimResponse(claim = instance, message = "Success"); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,29 @@ | |||||
| package com.ffii.tsms.modules.claim.web | |||||
| import com.ffii.tsms.modules.claim.entity.Claim | |||||
| import com.ffii.tsms.modules.claim.service.ClaimService | |||||
| import com.ffii.tsms.modules.claim.web.models.SaveClaimRequest | |||||
| import com.ffii.tsms.modules.claim.web.models.SaveClaimResponse | |||||
| import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse | |||||
| import com.ffii.tsms.modules.project.web.models.SaveCustomerRequest | |||||
| import jakarta.validation.Valid | |||||
| import org.springframework.web.bind.annotation.GetMapping | |||||
| import org.springframework.web.bind.annotation.PostMapping | |||||
| import org.springframework.web.bind.annotation.RequestBody | |||||
| import org.springframework.web.bind.annotation.RequestMapping | |||||
| import org.springframework.web.bind.annotation.RestController | |||||
| @RestController | |||||
| @RequestMapping("/claim") | |||||
| class ClaimController(private val claimService: ClaimService) { | |||||
| @GetMapping | |||||
| fun allClaims(): List<Claim> { | |||||
| return claimService.allClaims() | |||||
| } | |||||
| @PostMapping("/save") | |||||
| fun saveClaim(@Valid @RequestBody saveClaimRequest: SaveClaimRequest): SaveClaimResponse { | |||||
| return claimService.saveClaim(saveClaimRequest) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,36 @@ | |||||
| package com.ffii.tsms.modules.claim.web.models | |||||
| import com.ffii.tsms.modules.claim.entity.ClaimDetail | |||||
| import jakarta.validation.constraints.NotBlank | |||||
| import org.springframework.web.multipart.MultipartFile | |||||
| import java.math.BigDecimal | |||||
| import java.time.LocalDate | |||||
| data class SupportingDocument ( | |||||
| val id: Long, | |||||
| val skey: String, | |||||
| val filename: String, | |||||
| ) | |||||
| data class Project ( | |||||
| val id: Long, | |||||
| ) | |||||
| data class SaveClaimDetailRequest ( | |||||
| val id: Long, | |||||
| val invoiceDate: LocalDate, | |||||
| val description: String, | |||||
| val project: Project, | |||||
| val amount: BigDecimal, | |||||
| val newSupportingDocument: MultipartFile?, | |||||
| val oldSupportingDocument: SupportingDocument? | |||||
| ) | |||||
| data class SaveClaimRequest ( | |||||
| @field:NotBlank(message = "Expense type cannot be empty") | |||||
| val expenseType: String, | |||||
| val addClaimDetails: List<SaveClaimDetailRequest>, | |||||
| val status: String, | |||||
| val id: Long?, | |||||
| ) | |||||
| @@ -0,0 +1,8 @@ | |||||
| package com.ffii.tsms.modules.claim.web.models | |||||
| import com.ffii.tsms.modules.claim.entity.Claim | |||||
| data class SaveClaimResponse( | |||||
| val claim: Claim, | |||||
| val message: String, | |||||
| ) | |||||
| @@ -117,7 +117,6 @@ open class StaffsService( | |||||
| staffRepository.save(staff) | staffRepository.save(staff) | ||||
| salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.id!!) | salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.id!!) | ||||
| logger.info(staff.id) | |||||
| return staff | return staff | ||||
| } | } | ||||
| @Transactional(rollbackFor = [Exception::class]) | @Transactional(rollbackFor = [Exception::class]) | ||||
| @@ -0,0 +1,39 @@ | |||||
| package com.ffii.tsms.modules.file.entity | |||||
| import com.ffii.core.entity.BaseEntity | |||||
| import com.ffii.core.entity.IdEntity | |||||
| import jakarta.persistence.Column | |||||
| import jakarta.persistence.Entity | |||||
| import jakarta.persistence.Table | |||||
| import jakarta.validation.constraints.NotNull | |||||
| import java.math.BigInteger | |||||
| @Entity | |||||
| @Table(name = "file") | |||||
| open class File : BaseEntity<Long>() { | |||||
| open val DEFAULT_UPLOAD_MAX_FILE_SIZE_MB: Int = 50 | |||||
| @NotNull | |||||
| @Column(name = "skey", length = 32) | |||||
| open var skey: String? = null | |||||
| @NotNull | |||||
| @Column(name = "filename", length = 255) | |||||
| open var filename: String? = null | |||||
| @NotNull | |||||
| @Column(name = "extension", length = 10) | |||||
| open var description: String? = null | |||||
| @NotNull | |||||
| @Column(name = "mimetype", length = 255) | |||||
| open var mimetype: String? = null | |||||
| @NotNull | |||||
| @Column(name = "filesize") | |||||
| open var filesize: Long? = null | |||||
| @Column(name = "remarks", length = 500) | |||||
| open var remarks: String? = null | |||||
| } | |||||
| @@ -0,0 +1,24 @@ | |||||
| package com.ffii.tsms.modules.file.entity | |||||
| import com.ffii.core.entity.BaseEntity | |||||
| import com.ffii.core.entity.IdEntity | |||||
| import jakarta.persistence.Column | |||||
| import jakarta.persistence.Entity | |||||
| import jakarta.persistence.JoinColumn | |||||
| import jakarta.persistence.OneToOne | |||||
| import jakarta.persistence.Table | |||||
| import jakarta.validation.constraints.NotNull | |||||
| @Entity | |||||
| @Table(name = "file_blob") | |||||
| open class FileBlob : BaseEntity<Long>() { | |||||
| @NotNull | |||||
| @OneToOne | |||||
| @JoinColumn(name = "fileId") | |||||
| open var file: File? = null | |||||
| @NotNull | |||||
| @Column(name = "bytes") | |||||
| open var bytes: ByteArray? = null | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| package com.ffii.tsms.modules.file.entity | |||||
| import com.ffii.core.support.AbstractRepository | |||||
| import org.springframework.data.repository.query.Param | |||||
| import java.util.Optional | |||||
| interface FileBlobRepository : AbstractRepository<FileBlob, Long> { | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| package com.ffii.tsms.modules.file.entity | |||||
| import com.ffii.core.entity.BaseEntity | |||||
| import com.ffii.core.entity.IdEntity | |||||
| import jakarta.persistence.Column | |||||
| import jakarta.persistence.Entity | |||||
| import jakarta.persistence.JoinColumn | |||||
| import jakarta.persistence.OneToOne | |||||
| import jakarta.persistence.Table | |||||
| import jakarta.validation.constraints.NotNull | |||||
| @Entity | |||||
| @Table(name = "file_ref") | |||||
| open class FileRef : BaseEntity<Long>() { | |||||
| @NotNull | |||||
| @Column(name = "refType", length = 20) | |||||
| open var refType: String? = null | |||||
| @NotNull | |||||
| @Column(name = "refId") | |||||
| open var refId: Long? = null | |||||
| @Column(name = "refCode") | |||||
| open var refCode: String? = null | |||||
| @NotNull | |||||
| @OneToOne | |||||
| @JoinColumn(name = "fileId") | |||||
| open var file: File? = null | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| package com.ffii.tsms.modules.file.entity | |||||
| import com.ffii.core.support.AbstractRepository | |||||
| interface FileRefRepository : AbstractRepository<FileRef, Long> { | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| package com.ffii.tsms.modules.file.entity | |||||
| import com.ffii.core.support.AbstractRepository | |||||
| interface FileRepository : AbstractRepository<File, Long> { | |||||
| } | |||||
| @@ -0,0 +1,326 @@ | |||||
| package com.ffii.tsms.modules.file.service | |||||
| import com.ffii.core.support.AbstractBaseEntityService | |||||
| import com.ffii.core.support.JdbcDao | |||||
| import com.ffii.core.utils.FileUtils | |||||
| import com.ffii.core.utils.NumberUtils | |||||
| import com.ffii.core.utils.Params | |||||
| import com.ffii.tsms.modules.file.entity.* | |||||
| import org.apache.commons.lang3.RandomStringUtils | |||||
| import org.springframework.stereotype.Service | |||||
| import org.springframework.transaction.annotation.Isolation | |||||
| import org.springframework.transaction.annotation.Transactional | |||||
| import org.springframework.web.multipart.MultipartFile | |||||
| import java.io.ByteArrayInputStream | |||||
| import java.io.IOException | |||||
| import java.util.* | |||||
| import java.util.Map | |||||
| import javax.imageio.ImageIO | |||||
| @Service | |||||
| open class FileService( | |||||
| private val jdbcDao: JdbcDao, | |||||
| private val fileRepository: FileRepository, | |||||
| private val fileRefRepository: FileRefRepository, | |||||
| private val fileBlobRepository: FileBlobRepository, | |||||
| ) : AbstractBaseEntityService<File, Long, FileRepository>(jdbcDao, fileRepository) { | |||||
| @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = [Exception::class], readOnly = false) | |||||
| open fun saveFile(instance: File): File { | |||||
| return fileRepository.save(instance) | |||||
| } | |||||
| @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = [Exception::class], readOnly = false) | |||||
| open fun saveFileBlob(instance: FileBlob): FileBlob { | |||||
| return fileBlobRepository.save(instance) | |||||
| } | |||||
| @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = [Exception::class], readOnly = false) | |||||
| open fun saveFileRef(instance: FileRef): FileRef { | |||||
| return fileRefRepository.save(instance) | |||||
| } | |||||
| @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = [Exception::class], readOnly = true) | |||||
| open fun findFileByIdAndKey(id: Long, skey: String): Optional<File> { | |||||
| return jdbcDao.queryForEntity( | |||||
| "SELECT * from file f where f.id = :id and f.skey = :skey ", | |||||
| Map.of("id", id, "skey", skey), File::class.java | |||||
| ) | |||||
| } | |||||
| @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = [Exception::class], readOnly = true) | |||||
| open fun findFileRefByTypeAndId(refType: String, refId: Long): List<FileRef> { | |||||
| return jdbcDao.queryForList( | |||||
| "SELECT * from file_ref where refType = :refType and refId = :refId order by id ", | |||||
| java.util.Map.of("refType", refType, "refId", refId), FileRef::class.java | |||||
| ) | |||||
| } | |||||
| @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = [Exception::class], readOnly = true) | |||||
| open fun findFileRefByFileId(fileId: Long): Optional<FileRef> { | |||||
| return jdbcDao.queryForEntity( | |||||
| "SELECT * from file_ref where fileId = :fileId", java.util.Map.of("fileId", fileId), | |||||
| FileRef::class.java | |||||
| ) | |||||
| } | |||||
| @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = [Exception::class], readOnly = true) | |||||
| open fun findFileBlobByFileId(fileId: Long): Optional<FileBlob> { | |||||
| return jdbcDao.queryForEntity( | |||||
| "SELECT * from file_blob fb where fb.fileId = :fileId ", | |||||
| java.util.Map.of("fileId", fileId), FileBlob::class.java | |||||
| ) | |||||
| } | |||||
| @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = [Exception::class], readOnly = true) | |||||
| open fun isFileExists(id: Long?, skey: String?): Boolean { | |||||
| val sql = ("SELECT" | |||||
| + " COUNT(1)" | |||||
| + " FROM file f" | |||||
| + " WHERE f.deleted = 0" | |||||
| + " AND f.id = :id" | |||||
| + " AND f.skey = :skey") | |||||
| val count = jdbcDao.queryForInt(sql, Map.of("id", id, "skey", skey)) | |||||
| return (count > 0) | |||||
| } | |||||
| @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = [Exception::class], readOnly = true) | |||||
| open fun searchFiles(args: MutableMap<String, Any>): MutableList<MutableMap<String, Any>>? { | |||||
| val sql = StringBuilder( | |||||
| "SELECT" | |||||
| + " f.id," | |||||
| + " f.filename," | |||||
| + " f.filesize," | |||||
| + " f.skey," | |||||
| + " fr.refId," | |||||
| + " fr.refType," | |||||
| + " f.created," | |||||
| + " u.name AS createdByName," | |||||
| + " f.description" | |||||
| + " FROM file f" | |||||
| + " LEFT JOIN file_ref fr ON f.id = fr.fileId" | |||||
| + " LEFT JOIN user u ON f.createdBy = u.id" | |||||
| + " WHERE f.deleted = 0" | |||||
| ) | |||||
| if (args.containsKey("filename")) sql.append(" AND f.filename = :filename") | |||||
| if (args.containsKey("refType")) sql.append(" AND fr.refType = :refType") | |||||
| if (args.containsKey("refId")) sql.append(" AND fr.refId = :refId") | |||||
| if (args.containsKey("startDate")) sql.append(" AND f.created >= :startDate") | |||||
| if (args.containsKey("endDate")) sql.append(" AND f.created < :endDate") | |||||
| sql.append(" ORDER BY f.created DESC") | |||||
| return jdbcDao.queryForList(sql.toString(), args) | |||||
| } | |||||
| /** | |||||
| * Delete `FileRef` by `fileId`, `refId`, | |||||
| * `refType`, and `skey` | |||||
| */ | |||||
| @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = [Exception::class], readOnly = false) | |||||
| open fun deleteFile(fileId: Long, refId: Long, refType: String, skey: String) { | |||||
| val args = | |||||
| Map.of("fileId", fileId, "refId", refId, "refType", refType, "skey", skey) | |||||
| jdbcDao.executeUpdate( | |||||
| ("DELETE FROM file_ref" | |||||
| + " WHERE fileId = :fileId" | |||||
| + " AND refId = :refId" | |||||
| + " AND refType = :refType" | |||||
| + " AND EXISTS (SELECT 1 FROM file WHERE id = file_ref.fileId AND skey = :skey)"), | |||||
| args | |||||
| ) | |||||
| } | |||||
| @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = [Exception::class], readOnly = false) | |||||
| open fun deleteFile(fileId: Long?, skey: String?, filename: String?) { | |||||
| val args = Map.of("fileId", fileId, "skey", skey, "filename", filename) | |||||
| jdbcDao.executeUpdate( | |||||
| ("DELETE FROM file_ref " | |||||
| + " WHERE fileId = :fileId " | |||||
| + " AND EXISTS (SELECT 1 FROM file WHERE id = file_ref.fileId AND skey = :skey AND filename = :filename)"), | |||||
| args | |||||
| ) | |||||
| } | |||||
| @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = [Exception::class], readOnly = false) | |||||
| open fun saveFile( | |||||
| filename: String, | |||||
| description: String, | |||||
| refType: String, | |||||
| refId: Long, | |||||
| refCode: String, | |||||
| bytes: ByteArray | |||||
| ): Long { | |||||
| val file: File = File() | |||||
| file.apply { | |||||
| this.filename = filename | |||||
| this.description = description | |||||
| mimetype = FileUtils.guessMimetype(filename) | |||||
| filesize = NumberUtils.toLong(bytes.size, 0L) | |||||
| skey = RandomStringUtils.randomAlphanumeric(32) | |||||
| } | |||||
| val fileBlob: FileBlob = FileBlob() | |||||
| fileBlob.apply { | |||||
| this.bytes = bytes | |||||
| } | |||||
| val fileRef: FileRef = FileRef() | |||||
| fileRef.apply { | |||||
| this.refId = refId | |||||
| this.refType = refType | |||||
| this.refCode = refCode | |||||
| } | |||||
| // save File | |||||
| fileRepository.save(file) | |||||
| // save FileBlob | |||||
| fileBlob.apply { | |||||
| this.file = file | |||||
| } | |||||
| fileBlobRepository.save(fileBlob) | |||||
| // save FileRef | |||||
| fileRef.apply { | |||||
| this.file = file | |||||
| } | |||||
| fileRefRepository.save(fileRef) | |||||
| return file.id!! | |||||
| } | |||||
| @Transactional(rollbackFor = [Exception::class]) | |||||
| @Throws(IOException::class) | |||||
| open fun uploadFile( | |||||
| multipartFile: MultipartFile, refId: Long?, refType: String?, refCode: String?, | |||||
| maxSize: Int | |||||
| ): MutableMap<String, Any> { | |||||
| val result: MutableMap<String, Any> = HashMap<String, Any>() | |||||
| // filesize < 50MB | |||||
| if (multipartFile.size > 0 && multipartFile.size <= maxSize * 1024 * 1024) { | |||||
| // DEBUG LOG | |||||
| logger.info("multipartFile.getSize() = " + multipartFile.size) | |||||
| val file: File = File() | |||||
| file.apply { | |||||
| filename = multipartFile.originalFilename | |||||
| mimetype = FileUtils.guessMimetype(multipartFile.originalFilename) | |||||
| filesize = multipartFile.size | |||||
| skey = RandomStringUtils.randomAlphanumeric(32) | |||||
| } | |||||
| // get height and width if mimetype is png or jpeg | |||||
| // if (AbstractView.CONTENT_TYPE_PNG.equals(file.getMimetype()) | |||||
| // || AbstractView.CONTENT_TYPE_JPEG.equals(file.getMimetype()) | |||||
| // ) { | |||||
| // val image = ImageIO.read(ByteArrayInputStream(fileBlob.getBytes())) | |||||
| // if (image != null) { | |||||
| // file.setImageHeight(image.height) | |||||
| // file.setImageWidth(image.width) | |||||
| // } | |||||
| // } | |||||
| // create UserFile | |||||
| saveFile(file) | |||||
| // create UserFileBlob | |||||
| val fileBlob: FileBlob = FileBlob() | |||||
| fileBlob.apply { | |||||
| bytes = multipartFile.bytes | |||||
| this.file = file | |||||
| } | |||||
| saveFileBlob(fileBlob) | |||||
| // create UserFileRef | |||||
| val fileRef: FileRef = FileRef() | |||||
| fileRef.apply { | |||||
| this.refId = refId | |||||
| this.refType = refType | |||||
| this.refCode = refCode | |||||
| this.file = file | |||||
| } | |||||
| saveFileRef(fileRef) | |||||
| result["fileId"] = file.id!! | |||||
| result["skey"] = file.skey!! | |||||
| result["filename"] = file.filename!! | |||||
| } else { | |||||
| result["success"] = java.lang.Boolean.FALSE | |||||
| result[Params.MSG] = "Upload Failed" | |||||
| } | |||||
| return result | |||||
| } | |||||
| @Transactional(rollbackFor = [Exception::class]) | |||||
| @Throws(IOException::class) | |||||
| open fun updateFile( | |||||
| multipartFile: MultipartFile, refId: Long?, refType: String?, refCode: String?, | |||||
| maxSize: Int, fileId: Long | |||||
| ): MutableMap<String, Any> { | |||||
| val result: MutableMap<String, Any> = HashMap<String, Any>() | |||||
| // filesize < 50MB | |||||
| if (multipartFile.size > 0 && multipartFile.size <= maxSize * 1024 * 1024) { | |||||
| // DEBUG LOG | |||||
| logger.info("multipartFile.getSize() = " + multipartFile.size) | |||||
| val file: File = fileRepository.findById(fileId).orElse(File()) | |||||
| file.apply { | |||||
| filename = multipartFile.originalFilename | |||||
| mimetype = FileUtils.guessMimetype(multipartFile.originalFilename) | |||||
| filesize = multipartFile.size | |||||
| skey = RandomStringUtils.randomAlphanumeric(32) | |||||
| } | |||||
| // create UserFile | |||||
| saveFile(file) | |||||
| // create UserFileBlob | |||||
| val fileBlob: FileBlob = findFileBlobByFileId(fileId).orElse(FileBlob()) | |||||
| fileBlob.apply { | |||||
| bytes = multipartFile.bytes | |||||
| this.file = file | |||||
| } | |||||
| saveFileBlob(fileBlob) | |||||
| // create UserFileRef | |||||
| val fileRef: FileRef = findFileRefByFileId(fileId).orElse(FileRef()) | |||||
| fileRef.apply { | |||||
| this.refId = refId | |||||
| this.refType = refType | |||||
| this.refCode = refCode | |||||
| this.file = file | |||||
| } | |||||
| saveFileRef(fileRef) | |||||
| result["fileId"] = file.id!! | |||||
| result["skey"] = file.skey!! | |||||
| result["filename"] = file.filename!! | |||||
| } else { | |||||
| result["success"] = java.lang.Boolean.FALSE | |||||
| result[Params.MSG] = "Update Failed" | |||||
| } | |||||
| return result | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,133 @@ | |||||
| package com.ffii.tsms.modules.file.web | |||||
| import com.ffii.core.utils.DateUtils | |||||
| import com.ffii.core.utils.NumberUtils | |||||
| import com.ffii.core.utils.StringUtils | |||||
| import com.ffii.tsms.modules.file.entity.File | |||||
| import com.ffii.tsms.modules.file.entity.FileBlob | |||||
| import com.ffii.tsms.modules.file.service.FileService | |||||
| import jakarta.servlet.http.HttpServletRequest | |||||
| import jakarta.servlet.http.HttpServletResponse | |||||
| import org.springframework.beans.factory.annotation.Autowired | |||||
| import java.text.DecimalFormat | |||||
| import java.util.zip.ZipEntry | |||||
| import java.util.zip.ZipOutputStream | |||||
| import org.apache.commons.logging.Log; | |||||
| import org.apache.commons.logging.LogFactory; | |||||
| import org.springframework.web.bind.annotation.GetMapping | |||||
| import org.springframework.web.bind.annotation.PathVariable | |||||
| import org.springframework.web.bind.annotation.RequestMapping | |||||
| import org.springframework.web.bind.annotation.RequestParam | |||||
| import org.springframework.web.bind.annotation.RestController | |||||
| import java.io.IOException | |||||
| import java.net.URLEncoder | |||||
| import java.util.Date | |||||
| @RestController | |||||
| @RequestMapping("/file") | |||||
| open class DownloadFileController (private val fileService: FileService) { | |||||
| private val logger: Log = LogFactory.getLog(javaClass) | |||||
| private val ZIP_NUMBERER : Boolean = false | |||||
| @GetMapping("/dl/{id}/{skey}/{filename}") | |||||
| @Throws(IOException::class) | |||||
| open fun download( | |||||
| request: HttpServletRequest, response: HttpServletResponse, @RequestParam(defaultValue = "false") dl: Boolean, | |||||
| @PathVariable id: Long, @PathVariable skey: String, @PathVariable filename: String | |||||
| ) { | |||||
| val file: File = fileService.findFileByIdAndKey(id, skey).get() | |||||
| if (file != null) { | |||||
| val fileBlob: FileBlob = fileService.findFileBlobByFileId(id).get() | |||||
| response.reset() | |||||
| response.contentType = file.mimetype | |||||
| response.setContentLength(NumberUtils.toInt(file.filesize, 0)) | |||||
| response.setHeader("Content-Transfer-Encoding", "binary") | |||||
| response.setHeader("Access-Control-Expose-Headers", "Content-Disposition") | |||||
| response.setHeader( | |||||
| "Content-Disposition", | |||||
| java.lang.String.format( | |||||
| "%s; filename=\"%s\"", | |||||
| if (dl) "attachment" else "inline", | |||||
| URLEncoder.encode(file.filename, "UTF-8") | |||||
| ) | |||||
| ) | |||||
| // String origin = request.getHeader("Origin"); | |||||
| //TODO: anna Access-Control-Allow-Origin | |||||
| response.addHeader("Access-Control-Allow-Origin", "*") | |||||
| response.setHeader("Access-Control-Allow-Methods", "OPTIONS, GET") | |||||
| val out = response.outputStream | |||||
| try { | |||||
| out.write(fileBlob.bytes!!) | |||||
| out.flush() | |||||
| out.close() | |||||
| } catch (e: IOException) { | |||||
| logger.warn(e.message) | |||||
| } finally { | |||||
| out.close() | |||||
| } | |||||
| } else { | |||||
| logger.info("*** 400 BAD REQUEST ***") | |||||
| response.status = HttpServletResponse.SC_BAD_REQUEST | |||||
| } | |||||
| } | |||||
| @GetMapping(value = ["/dlz"], produces = ["application/zip"]) | |||||
| @Throws(IOException::class) | |||||
| open fun downloadAsZip( | |||||
| response: HttpServletResponse, | |||||
| @RequestParam filename: String, @RequestParam ids: String, @RequestParam skeys: String | |||||
| ) { | |||||
| var filename = filename | |||||
| filename += DateUtils.formatDate(Date(), "_yyyy_MM_dd_HHmm", "") + ".zip" | |||||
| response.setHeader("Access-Control-Expose-Headers", "Content-Disposition") | |||||
| response.setHeader("Content-Transfer-Encoding", "binary") | |||||
| response.setHeader( | |||||
| "Content-Disposition", | |||||
| String.format("%s; filename=\"%s\"", "attachment", response.encodeURL(filename)) | |||||
| ) | |||||
| val df00 = DecimalFormat("00") | |||||
| val zipOutputStream = ZipOutputStream(response.outputStream) | |||||
| val idsA: Array<String> = StringUtils.split(ids, ',') | |||||
| val skeysA: Array<String> = StringUtils.split(skeys, ',') | |||||
| var filePrefixIdx = 1 | |||||
| for (i in idsA.indices) { | |||||
| val id: Long = NumberUtils.longValue(idsA[i]) | |||||
| val skey = skeysA[i] | |||||
| val file: File = fileService.findFileByIdAndKey(id, skey).get() | |||||
| if (file == null) { | |||||
| logger.warn("*** file is null, id = $id, skey = $skey") | |||||
| } else { | |||||
| val fileBlob: FileBlob = fileService.findFileBlobByFileId(id).get() | |||||
| if (fileBlob == null) { | |||||
| logger.warn("*** fileBlob is null, id = $id, skey = $skey") | |||||
| } else { | |||||
| zipOutputStream.putNextEntry( | |||||
| ZipEntry( | |||||
| if (ZIP_NUMBERER) (df00.format(filePrefixIdx++.toLong()) + "_" + file.filename) | |||||
| else file.filename | |||||
| ) | |||||
| ) | |||||
| zipOutputStream.write(fileBlob.bytes) | |||||
| zipOutputStream.closeEntry() | |||||
| } | |||||
| } | |||||
| } | |||||
| zipOutputStream.flush() | |||||
| zipOutputStream.close() | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,67 @@ | |||||
| package com.ffii.tsms.modules.file.web | |||||
| import com.ffii.core.response.RecordsRes | |||||
| import com.ffii.core.utils.Params | |||||
| import com.ffii.tsms.modules.file.entity.File | |||||
| import com.ffii.tsms.modules.file.service.FileService | |||||
| import com.ffii.tsms.modules.file.web.model.FileReq | |||||
| import com.ffii.tsms.modules.file.web.model.UpdateFileReq | |||||
| import jakarta.servlet.http.HttpServletRequest | |||||
| import jakarta.servlet.http.HttpServletResponse | |||||
| import jakarta.validation.Valid | |||||
| import org.springframework.ui.Model | |||||
| import org.springframework.web.bind.ServletRequestBindingException | |||||
| import org.springframework.web.bind.annotation.GetMapping | |||||
| import org.springframework.web.bind.annotation.PathVariable | |||||
| import org.springframework.web.bind.annotation.PostMapping | |||||
| import org.springframework.web.bind.annotation.RequestBody | |||||
| import org.springframework.web.bind.annotation.RequestMapping | |||||
| import org.springframework.web.bind.annotation.RestController | |||||
| @RestController | |||||
| @RequestMapping(value = ["/file"]) | |||||
| open class FileController (private val fileService: FileService) { | |||||
| @PostMapping("/list") | |||||
| @Throws(ServletRequestBindingException::class) | |||||
| fun listJson( | |||||
| model: Model?, | |||||
| request: HttpServletRequest?, | |||||
| @RequestBody req: @Valid FileReq | |||||
| ): RecordsRes<Map<String, Any>> { | |||||
| val args: MutableMap<String, Any> = HashMap() | |||||
| args["refType"] = req.refType | |||||
| args["refId"] = req.refId | |||||
| val records: List<Map<String, Any>>? = fileService.searchFiles(args) | |||||
| return RecordsRes(records) | |||||
| } | |||||
| @PostMapping("/update") | |||||
| fun update( | |||||
| model: Model?, | |||||
| response: HttpServletResponse?, | |||||
| @RequestBody req: @Valid UpdateFileReq | |||||
| ): Map<String, Any> { | |||||
| val file: File = fileService.findFileByIdAndKey(req.fileId, req.skey).get() | |||||
| val result: MutableMap<String, Any> = HashMap() | |||||
| if (file != null) { | |||||
| file.apply { | |||||
| filename = req.filename | |||||
| description = req.description | |||||
| } | |||||
| fileService.saveFile(file) | |||||
| result[Params.SUCCESS] = true | |||||
| } else { | |||||
| result[Params.SUCCESS] = false | |||||
| } | |||||
| return result | |||||
| } | |||||
| @GetMapping("/delete/{fileId}/{skey}/{filename}") | |||||
| fun delete(@PathVariable fileId: Long, @PathVariable skey: String, @PathVariable filename: String) { | |||||
| fileService.deleteFile(fileId, skey, filename) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,45 @@ | |||||
| package com.ffii.tsms.modules.file.web | |||||
| import com.ffii.core.utils.StringUtils | |||||
| import com.ffii.tsms.modules.file.entity.File | |||||
| import com.ffii.tsms.modules.file.service.FileService | |||||
| import com.ffii.tsms.modules.settings.service.SettingsService | |||||
| import jakarta.servlet.http.HttpServletRequest | |||||
| import org.apache.commons.logging.Log | |||||
| import org.apache.commons.logging.LogFactory | |||||
| import org.springframework.ui.Model | |||||
| import org.springframework.web.bind.ServletRequestBindingException | |||||
| import org.springframework.web.bind.annotation.PostMapping | |||||
| import org.springframework.web.bind.annotation.RequestMapping | |||||
| import org.springframework.web.bind.annotation.RequestParam | |||||
| import org.springframework.web.bind.annotation.RestController | |||||
| import org.springframework.web.multipart.MultipartFile | |||||
| import java.io.IOException | |||||
| @RestController | |||||
| @RequestMapping(value = ["/file"]) | |||||
| class UploadFileController(private val settingsService: SettingsService, private val fileService: FileService) { | |||||
| private val logger: Log = LogFactory.getLog(javaClass) | |||||
| @PostMapping("/ul") | |||||
| @Throws(ServletRequestBindingException::class, IOException::class) | |||||
| fun uploadFile( | |||||
| request: HttpServletRequest, | |||||
| model: Model, | |||||
| @RequestParam refId: Long, | |||||
| @RequestParam refType: String, | |||||
| @RequestParam(defaultValue = StringUtils.EMPTY) refCode: String, | |||||
| @RequestParam multipartFileList: List<MultipartFile> | |||||
| ): Map<String, Any> { | |||||
| var result: Map<String, Any> = HashMap<String, Any>() | |||||
| if (multipartFileList != null) { | |||||
| for (multipartFile in multipartFileList) { | |||||
| result = fileService.uploadFile(multipartFile, refId, refType, refCode, File().DEFAULT_UPLOAD_MAX_FILE_SIZE_MB) | |||||
| } | |||||
| } | |||||
| // only proceed if multipartFile is not null, and has file size | |||||
| return result | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| package com.ffii.tsms.modules.file.web.model | |||||
| data class FileReq( | |||||
| val fileId: Long, | |||||
| val fileName: String, | |||||
| val refType: String, | |||||
| val refId: String, | |||||
| val refCode: String, | |||||
| val skey: String, | |||||
| ) | |||||
| @@ -0,0 +1,8 @@ | |||||
| package com.ffii.tsms.modules.file.web.model | |||||
| data class UpdateFileReq ( | |||||
| val fileId: Long, | |||||
| val filename: String, | |||||
| val skey: String, | |||||
| val description: String, | |||||
| ) | |||||
| @@ -134,6 +134,17 @@ public class SettingsService extends AbstractIdEntityService<Settings, Long, Set | |||||
| .orElseThrow(InternalServerErrorException::new); | .orElseThrow(InternalServerErrorException::new); | ||||
| } | } | ||||
| public int getInt(String name, int defaultValue) { | |||||
| return this.findByName(name) | |||||
| .map(Settings::getValue) | |||||
| .map(v -> { | |||||
| try { | |||||
| return Integer.parseInt(v); | |||||
| } catch (final NumberFormatException nfe) { | |||||
| return defaultValue; | |||||
| } | |||||
| }).orElse(defaultValue); | |||||
| } | |||||
| public double getDouble(String name) { | public double getDouble(String name) { | ||||
| return this.findByName(name) | return this.findByName(name) | ||||
| .map(Settings::getValue) | .map(Settings::getValue) | ||||
| @@ -0,0 +1,13 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:update claim | |||||
| ALTER TABLE `claim` | |||||
| CHANGE COLUMN `decision` `type` VARCHAR(20) NOT NULL , | |||||
| ADD INDEX `FK_CLAIM_ON_VERIFIEDBY` (`verifiedBy` ASC) VISIBLE; | |||||
| ; | |||||
| ALTER TABLE `claim` | |||||
| ADD CONSTRAINT `FK_CLAIM_ON_VERIFIEDBY` | |||||
| FOREIGN KEY (`verifiedBy`) | |||||
| REFERENCES `staff` (`id`) | |||||
| ON DELETE NO ACTION | |||||
| ON UPDATE NO ACTION; | |||||
| @@ -0,0 +1,50 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:create claim_detail, update claim | |||||
| CREATE TABLE `claim_detail` ( | |||||
| `id` INT NOT NULL AUTO_INCREMENT, | |||||
| `version` INT NOT NULL DEFAULT '0', | |||||
| `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||||
| `createdBy` VARCHAR(30) NULL DEFAULT NULL, | |||||
| `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||||
| `modifiedBy` VARCHAR(30) NULL DEFAULT NULL, | |||||
| `deleted` TINYINT(1) NOT NULL DEFAULT '0', | |||||
| `claimId` INT NOT NULL, | |||||
| `invoiceDate` DATETIME NOT NULL, | |||||
| `projectId` INT NOT NULL, | |||||
| `description` VARCHAR(255) NOT NULL, | |||||
| `amount` DECIMAL(14,2) NOT NULL, | |||||
| `fileId` INT NOT NULL, | |||||
| PRIMARY KEY (`id`), | |||||
| INDEX `FK_CLAIM_DETAIL_ON_CLAIMID` (`claimId` ASC) VISIBLE, | |||||
| INDEX `FK_CLAIM_DETAIL_ON_PROJECTID_` (`projectId` ASC) VISIBLE, | |||||
| INDEX `FK_CLAIM_DETAIL_ON_FILEID` (`fileId` ASC) VISIBLE, | |||||
| CONSTRAINT `FK_CLAIM_DETAIL_ON_CLAIMID` | |||||
| FOREIGN KEY (`claimId`) | |||||
| REFERENCES `claim` (`id`) | |||||
| ON DELETE NO ACTION | |||||
| ON UPDATE NO ACTION, | |||||
| CONSTRAINT `FK_CLAIM_DETAIL_ON_PROJECTID` | |||||
| FOREIGN KEY (`projectId`) | |||||
| REFERENCES `project` (`id`) | |||||
| ON DELETE NO ACTION | |||||
| ON UPDATE NO ACTION, | |||||
| CONSTRAINT `FK_CLAIM_DETAIL_ON_FILEID` | |||||
| FOREIGN KEY (`fileId`) | |||||
| REFERENCES `file` (`id`) | |||||
| ON DELETE NO ACTION | |||||
| ON UPDATE NO ACTION); | |||||
| ALTER TABLE `claim` | |||||
| DROP FOREIGN KEY `FK_CLAIM_ON_PROJECTID`, | |||||
| DROP FOREIGN KEY `FK_CLAIM_ON_FILEID`; | |||||
| ALTER TABLE `claim` | |||||
| DROP COLUMN `approvedAmount`, | |||||
| DROP COLUMN `fileId`, | |||||
| DROP COLUMN `amount`, | |||||
| DROP COLUMN `description`, | |||||
| DROP COLUMN `projectId`, | |||||
| ADD COLUMN `status` VARCHAR(30) NOT NULL AFTER `type`, | |||||
| DROP INDEX `FK_CLAIM_ON_FILEID` , | |||||
| DROP INDEX `FK_CLAIM_ON_PROJECTID` ; | |||||
| ; | |||||
| @@ -0,0 +1,6 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:update claim | |||||
| ALTER TABLE `claim` | |||||
| ; | |||||
| ALTER TABLE `claim` ALTER INDEX `FK_CLAIM_ON_STAFFID` VISIBLE; | |||||
| @@ -0,0 +1,5 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:update claim | |||||
| ALTER TABLE `claim_detail` | |||||
| CHANGE COLUMN `invoiceDate` `invoiceDate` DATE NOT NULL ; | |||||
| @@ -0,0 +1,5 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:update claim | |||||
| ALTER TABLE `claim` | |||||
| ADD COLUMN `code` VARCHAR(30) NULL AFTER `deleted`; | |||||