add image preview

This commit is contained in:
Shaun Chyxion 2018-03-18 22:15:30 +08:00
parent 267fb19f2e
commit 2bf6f74181
37 changed files with 767 additions and 378 deletions

View File

@ -1,17 +1,20 @@
package com.pudonghot.ambition.crm.controller; package com.pudonghot.ambition.crm.controller;
import java.util.Map; import java.util.*;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collections; import java.lang.reflect.Array;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.chyxion.tigon.model.M1;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import me.chyxion.tigon.mybatis.Search; import me.chyxion.tigon.mybatis.Search;
import com.alibaba.fastjson.JSONException; import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.util.TypeUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.CharEncoding;
@ -107,12 +110,11 @@ public abstract class AbstractBaseController {
*/ */
protected InputStream getInputStream(MultipartFile file) { protected InputStream getInputStream(MultipartFile file) {
try { try {
return file == null || file.isEmpty() ? return file != null && !file.isEmpty() ? file.getInputStream() : null;
null : file.getInputStream();
} }
catch (IOException e) { catch (IOException e) {
throw new IllegalStateException( throw new IllegalStateException(
"Read Multipart File Stream Error Caused", e); "Read multipart file stream error caused", e);
} }
} }
@ -123,13 +125,13 @@ public abstract class AbstractBaseController {
protected String decodeParam(String param) { protected String decodeParam(String param) {
if (StringUtils.isNotBlank(param)) { if (StringUtils.isNotBlank(param)) {
try { try {
log.debug("Decode Param [{}].", param); log.debug("Decode param [{}].", param);
param = URLDecoder.decode(param, CharEncoding.UTF_8); param = URLDecoder.decode(param, CharEncoding.UTF_8);
log.debug("Decode Param Result [{}].", param); log.debug("Decode param result [{}].", param);
} }
catch (UnsupportedEncodingException e) { catch (UnsupportedEncodingException e) {
throw new IllegalStateException( throw new IllegalStateException(
"Decode [" + param + "] Error Caused", e); "Decode [" + param + "] error caused", e);
} }
} }
return param; return param;
@ -139,14 +141,12 @@ public abstract class AbstractBaseController {
* @param start start * @param start start
* @param limit limit * @param limit limit
* @param search search * @param search search
* @param strSorters sorters
* @return search * @return search
*/ */
protected Search search(final Integer start, protected Search search(final Integer start,
final Integer limit, final Integer limit,
final String search, final String search) {
final String strSorters) { return search(null, start, limit, search, null, null, null);
return search(null, start, limit, search, strSorters);
} }
/** /**
@ -155,14 +155,16 @@ public abstract class AbstractBaseController {
* @param start start * @param start start
* @param limit limit * @param limit limit
* @param strSearch search * @param strSearch search
* @param strSorters sorters * @param strOrders orders
* @return search * @return search
*/ */
protected Search search(Search search, protected Search search(Search search,
final Integer start, final Integer start,
final Integer limit, final Integer limit,
final String strSearch, final String strSearch,
final String strSorters) { final String strCriteria,
final String strFilters,
final String strOrders) {
if (search == null) { if (search == null) {
search = new Search(); search = new Search();
@ -175,27 +177,140 @@ public abstract class AbstractBaseController {
} }
if (StringUtils.isNotBlank(strSearch)) { if (StringUtils.isNotBlank(strSearch)) {
final Search orSearch = new Search(); final Search orSearch = new Search();
for (String col : searchCols()) { for (final String col : searchCols()) {
orSearch.or(new Search().like(col, decodeLike(strSearch))); orSearch.or(new Search().like(col, decodeLike(strSearch)));
} }
search.and(orSearch); search.and(orSearch);
} }
if (StringUtils.isNotBlank(strSorters)) { criteria(search, strCriteria);
Map<String, String> sorterCols = sorterCols(); filters(search, strFilters);
final JSONArray jaSorters; order(search, strOrders) ;
return search;
}
protected Search criteria(final Search search, final String strCriteria) {
if (StringUtils.isBlank(strCriteria)) {
log.debug("No criteria given.");
return search;
}
final List<Object[]> criteria;
try { try {
jaSorters = JSON.parseArray(decodeParam(strSorters)); criteria = JSON.parseArray(decodeParam(strCriteria), Object[].class);
}
catch (Exception e) {
throw new IllegalStateException(
"Invalid criteria [" + strCriteria + "]", e);
}
for (final Object[] criterion : criteria) {
if (criterion.length == 3) {
final String field = (String) criterion[0];
if (StringUtils.isNotBlank(field)) {
final Pair<String, Class<?>> colProp = criterionCol(field);
final String col = colProp.getKey();
if (StringUtils.isNotBlank(col)) {
final String op = (String) criterion[1];
final Object val = TypeUtils.castToJavaBean(criterion[2], colProp.getValue());
if (StringUtils.isNotBlank(op) &&
val != null && val instanceof String ?
StringUtils.isNotBlank((String) val) : true) {
onSearch(search, col);
if ("eq".equals(op)) {
search.eq(col, val);
}
else if ("gt".equals(op)) {
search.gt(col, val);
}
else if ("gte".equals(op)) {
search.gte(col, val);
}
else if ("lt".equals(op)) {
search.lt(col, val);
}
else if ("lte".equals(op)) {
search.lt(col, val);
}
else if ("ne".equals(op)) {
search.ne(col, val);
}
else if ("like".equals(op)) {
search.like(col, (String) val, true);
}
else {
log.warn("Unknown criterion op [{}], ignore.", op);
}
}
}
}
else {
log.warn("Criterion col is blank, ignore.");
}
}
else {
log.warn("Invalid criterion [{}], ignore.", criterion);
}
}
return search;
}
protected JSONObject filters(String filters) {
try {
return JSON.parseObject(decodeParam(filters));
}
catch (Exception e) {
throw new IllegalStateException(
"Invalid filters [" + filters + "]", e);
}
}
protected Search filters(final Search search, final String strFilters) {
if (StringUtils.isBlank(strFilters)) {
log.debug("No filters given, ignore.");
return defaultFilter(search);
}
final JSONObject joFilters = filters(strFilters);
for (Map.Entry<String, Object> filter : joFilters.entrySet()) {
final String field = filter.getKey();
final Pair<String, Class<?>> colProp = filterCol(filter.getKey());
if (colProp != null) {
final String col = colProp.getKey();
final Object filterVal = filter.getValue();
if (filterVal != null) {
onSearch(search, col);
// type convert
if (filterVal instanceof JSONArray) {
search.eq(col, joFilters.getObject(
field, Array.newInstance(colProp.getValue(), 0).getClass()));
}
else {
search.eq(col, filterVal);
}
}
}
return search;
}
return search;
}
protected Search order(final Search search, final String strOrders) {
if (StringUtils.isNotBlank(strOrders)) {
final JSONArray jaOrders;
try {
jaOrders = JSON.parseArray(decodeParam(strOrders));
} }
catch (JSONException e) { catch (JSONException e) {
throw new IllegalStateException( throw new IllegalStateException(
"Invalid Sorters JSONArray Params [" + strSorters + "]", e); "Invalid orders params [" + strOrders + "]", e);
} }
for (Object objSorter : jaSorters) { for (final Object objOrder : jaOrders) {
final JSONObject joSorter = (JSONObject) objSorter; final JSONObject joSorter = (JSONObject) objOrder;
if (!joSorter.isEmpty()) { if (!joSorter.isEmpty()) {
final Map.Entry<String, Object> sortEntry = final Map.Entry<String, Object> sortEntry =
joSorter.entrySet().iterator().next(); joSorter.entrySet().iterator().next();
final String col = sorterCols.get(sortEntry.getKey()); final String col = orderCol(sortEntry.getKey());
if (StringUtils.isNotBlank(col)) { if (StringUtils.isNotBlank(col)) {
final String dir = (String) sortEntry.getValue(); final String dir = (String) sortEntry.getValue();
if ("asc".equalsIgnoreCase(dir)) { if ("asc".equalsIgnoreCase(dir)) {
@ -208,20 +323,48 @@ public abstract class AbstractBaseController {
} }
} }
} }
else {
defaultOrder(search);
}
return search; return search;
} }
/** /**
* @return simple search cols * @return simple search cols
*/ */
protected String[] searchCols() { protected List<String> searchCols() {
return new String[]{ }; return Collections.emptyList();
} }
/** protected Search defaultFilter(final Search search) {
* @return simple search cols return search;
*/ }
protected Map<String, String> sorterCols() {
return Collections.emptyMap(); protected Search defaultOrder(final Search search) {
return search;
}
protected Pair<String, Class<?>> criterionCol(final String field) {
return null;
}
protected Pair<String, Class<?>> filterCol(final String field) {
return null;
}
protected String orderCol(final String field) {
return null;
}
protected void onSearch(final Search search, final String col) {
}
protected Map<String, String> defaultOrderCols() {
final Map<String, String> cols = new HashMap<>();
cols.put(M1.ID, M1.ID);
cols.put("dateCreated", M1.DATE_CREATED);
cols.put("dateUpdated", M1.DATE_UPDATED);
return cols;
} }
} }

View File

@ -1,5 +1,7 @@
package com.pudonghot.ambition.crm.controller; package com.pudonghot.ambition.crm.controller;
import java.util.List;
import java.util.Arrays;
import javax.validation.Valid; import javax.validation.Valid;
import me.chyxion.tigon.mybatis.Search; import me.chyxion.tigon.mybatis.Search;
import javax.validation.constraints.Max; import javax.validation.constraints.Max;
@ -8,14 +10,18 @@ import me.chyxion.tigon.model.ViewModel;
import me.chyxion.tigon.model.ListResult; import me.chyxion.tigon.model.ListResult;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.pudonghot.ambition.crm.model.User; import com.pudonghot.ambition.crm.model.User;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import com.pudonghot.ambition.crm.model.Application; import com.pudonghot.ambition.crm.model.Application;
import com.pudonghot.ambition.crm.service.UserService;
import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresRoles;
import com.pudonghot.ambition.crm.model.CustomerProperty; import com.pudonghot.ambition.crm.model.CustomerProperty;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import com.pudonghot.ambition.crm.service.ApplicationService; import com.pudonghot.ambition.crm.service.ApplicationService;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import com.pudonghot.ambition.crm.form.create.ApplicationFormForCreate; import com.pudonghot.ambition.crm.form.create.ApplicationFormForCreate;
import com.pudonghot.ambition.crm.form.update.ApplicationFormForUpdate; import com.pudonghot.ambition.crm.form.update.ApplicationFormForUpdate;
@ -29,6 +35,14 @@ import com.pudonghot.ambition.crm.form.update.ApplicationFormForUpdate;
public class ApplicationController public class ApplicationController
extends BaseQueryController<Application> { extends BaseQueryController<Application> {
@Value("${file.base-path}")
private String fileBasePath;
@Autowired
private UserService userService;
private final List<String> SEARCH_COLS =
Arrays.asList(CustomerProperty.NAME,
CustomerProperty.NOTE);
@RequestMapping("/list") @RequestMapping("/list")
public ListResult<ViewModel<Application>> list( public ListResult<ViewModel<Application>> list(
@Min(0) @Min(0)
@ -38,6 +52,8 @@ public class ApplicationController
@Max(2048) @Max(2048)
@RequestParam(value = "limit", defaultValue = "16") @RequestParam(value = "limit", defaultValue = "16")
final int limit, final int limit,
@RequestParam(value = "filters", required = false)
final String filters,
@RequestParam(value = "search", required = false) @RequestParam(value = "search", required = false)
final String strSearch) { final String strSearch) {
@ -47,8 +63,12 @@ public class ApplicationController
if (!user.isAdmin()) { if (!user.isAdmin()) {
search.eq(Application.OWNER, user.getId()); search.eq(Application.OWNER, user.getId());
} }
final ListResult<ViewModel<Application>> result =
return listViewModels(search, start, limit, strSearch, null); listViewModels(search, start, limit, strSearch, null, filters, null);
result.setAttr("fileBasePath", fileBasePath);
result.setAttr("users", userService.listViewModels(
new Search().asc(User.EMPLOYEE_ID)));
return result;
} }
@RequestMapping("/list-for-select") @RequestMapping("/list-for-select")
@ -63,7 +83,7 @@ public class ApplicationController
if (StringUtils.isNotBlank(enabled)) { if (StringUtils.isNotBlank(enabled)) {
search.eq(CustomerProperty.ENABLED, Boolean.parseBoolean(enabled)); search.eq(CustomerProperty.ENABLED, Boolean.parseBoolean(enabled));
} }
return listViewModels(search, null, null, strSearch, null); return listViewModels(search, null, null, strSearch, null, null, null);
} }
@RequiresRoles(User.ROLE_ADMIN) @RequiresRoles(User.ROLE_ADMIN)
@ -77,6 +97,7 @@ public class ApplicationController
@RequestMapping(value = "/update", method = RequestMethod.POST) @RequestMapping(value = "/update", method = RequestMethod.POST)
public void update( public void update(
@Valid ApplicationFormForUpdate form) { @Valid ApplicationFormForUpdate form) {
form.setAdmin(getAuthUser().getUser().getData().isAdmin());
((ApplicationService) queryService).update(form); ((ApplicationService) queryService).update(form);
} }
@ -84,10 +105,15 @@ public class ApplicationController
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
protected String[] searchCols() { protected List<String> searchCols() {
return new String[] { return SEARCH_COLS;
CustomerProperty.NAME, }
CustomerProperty.NOTE
}; /**
* {@inheritDoc}
*/
@Override
protected Pair<String, Class<?>> filterCol(final String field) {
return Application.OWNER.equals(field) ? Pair.of(field, String.class) : null;
} }
} }

View File

@ -1,11 +1,12 @@
package com.pudonghot.ambition.crm.controller; package com.pudonghot.ambition.crm.controller;
import lombok.extern.slf4j.Slf4j;
import me.chyxion.tigon.mybatis.Search; import me.chyxion.tigon.mybatis.Search;
import me.chyxion.tigon.model.BaseModel; import me.chyxion.tigon.model.BaseModel;
import me.chyxion.tigon.model.ViewModel; import me.chyxion.tigon.model.ViewModel;
import me.chyxion.tigon.model.ListResult; import me.chyxion.tigon.model.ListResult;
import javax.validation.constraints.NotNull;
import me.chyxion.tigon.service.BaseQueryService; import me.chyxion.tigon.service.BaseQueryService;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -17,12 +18,31 @@ import org.springframework.web.bind.annotation.RequestMapping;
* chyxion@163.com <br> * chyxion@163.com <br>
* May 10, 2016 4:50:58 PM * May 10, 2016 4:50:58 PM
*/ */
@Slf4j
public class BaseQueryController<Model extends BaseModel<String>> public class BaseQueryController<Model extends BaseModel<String>>
extends BaseController { extends BaseController {
@Autowired @Autowired
protected BaseQueryService<String, Model> queryService; protected BaseQueryService<String, Model> queryService;
/**
* find model by id
* @param id model id
* @return model
*/
@RequestMapping("/find")
public ViewModel<Model> find(@NotBlank @RequestParam("id") String id) {
return queryService.findViewModel(id);
}
/**
* @return models count
*/
@RequestMapping("/count")
public int count() {
return queryService.count(null);
}
/** /**
* list view models * list view models
* @param start start * @param start start
@ -34,40 +54,48 @@ public class BaseQueryController<Model extends BaseModel<String>>
final Integer start, final Integer start,
final Integer limit, final Integer limit,
final String paramSearch) { final String paramSearch) {
return listViewModels(start, limit, paramSearch, null); return listViewModels(start, limit, paramSearch, null, null, null);
} }
/** /**
* list view models
* @param start start * @param start start
* @param limit limit * @param limit limit
* @param strSearch search * @param strSearch search
* @param strSorters sorters * @param strCriteria criteria
* @return search * @param strFilters filters
* @param strOrders orders
* @return list result
*/ */
protected ListResult<ViewModel<Model>> listViewModels( protected ListResult<ViewModel<Model>> listViewModels(
final Integer start, final Integer start,
final Integer limit, final Integer limit,
final String strSearch, final String strSearch,
final String strSorters) { final String strCriteria,
return listViewModels(null, start, limit, strSearch, strSorters); final String strFilters,
final String strOrders) {
return listViewModels(null, start, limit, strSearch, strCriteria, strFilters, strOrders);
} }
/** /**
* list view models
* @param search search * @param search search
* @param start start * @param start start
* @param limit limit * @param limit limit
* @param strSearch search * @param strSearch search
* @return view models * @param strCriteria criteria
* @param strFilters filters
* @param strOrders orders
* @return list result
*/ */
protected ListResult<ViewModel<Model>> listViewModels( protected ListResult<ViewModel<Model>> listViewModels(
final Search search, final Search search,
final Integer start, final Integer start,
final Integer limit, final Integer limit,
final String strSearch, final String strSearch,
final String strSorters) { final String strCriteria,
return listViewModels(search(search, start, limit, strSearch, strSorters)); final String strFilters,
final String strOrders) {
return listViewModels(search(
search, start, limit, strSearch, strCriteria, strFilters, strOrders));
} }
/** /**
@ -78,22 +106,4 @@ public class BaseQueryController<Model extends BaseModel<String>>
protected ListResult<ViewModel<Model>> listViewModels(Search search) { protected ListResult<ViewModel<Model>> listViewModels(Search search) {
return queryService.listViewModelsPage(search); return queryService.listViewModelsPage(search);
} }
/**
* find model by id
* @param id model id
* @return model
*/
@RequestMapping("/find")
public ViewModel<Model> find(@NotNull @RequestParam("id") String id) {
return queryService.findViewModel(id);
}
/**
* @return models count
*/
@RequestMapping("/count")
public int count() {
return queryService.count(null);
}
} }

View File

@ -2,9 +2,8 @@ package com.pudonghot.ambition.crm.controller;
import java.util.*; import java.util.*;
import javax.validation.Valid; import javax.validation.Valid;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import com.alibaba.fastjson.JSONObject;
import me.chyxion.tigon.mybatis.Search; import me.chyxion.tigon.mybatis.Search;
import me.chyxion.tigon.model.ViewModel; import me.chyxion.tigon.model.ViewModel;
import javax.validation.constraints.Max; import javax.validation.constraints.Max;
@ -13,6 +12,7 @@ import me.chyxion.tigon.model.ListResult;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.pudonghot.ambition.crm.model.User; import com.pudonghot.ambition.crm.model.User;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import com.pudonghot.ambition.crm.model.Customer; import com.pudonghot.ambition.crm.model.Customer;
import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresRoles;
@ -37,7 +37,7 @@ import com.pudonghot.ambition.crm.form.update.CustomerFormForUpdate;
public class CustomerController public class CustomerController
extends BaseQueryController<Customer> { extends BaseQueryController<Customer> {
private static final String[] SEARCH_COLS = new String[] { private static final List<String> SEARCH_COLS = Arrays.asList(
"customer.id", "customer.id",
"customer.name", "customer.name",
"customer.city", "customer.city",
@ -48,26 +48,25 @@ public class CustomerController
"customer.salesperson", "customer.salesperson",
"year", "year",
// status text // status text
"prop.name" "prop.name");
};
private static final Map<String, String> SORT_COLS = new HashMap<>(); private static final Map<String, String> ORDER_COLS = new HashMap<>();
static { static {
SORT_COLS.put(Customer.ID, "customer.id"); ORDER_COLS.put(Customer.ID, "customer.id");
SORT_COLS.put("dateAdded", "year(customer.date_added)"); ORDER_COLS.put("dateAdded", "year(customer.date_added)");
SORT_COLS.put(CustomerYearToDateSale.YEAR, CustomerYearToDateSale.YEAR); ORDER_COLS.put(CustomerYearToDateSale.YEAR, CustomerYearToDateSale.YEAR);
SORT_COLS.put("countryCode", "customer.country_code"); ORDER_COLS.put("countryCode", "customer.country_code");
SORT_COLS.put(Customer.MS, "customer.ms"); ORDER_COLS.put(Customer.MS, "customer.ms");
SORT_COLS.put(Customer.REGION, "customer.region"); ORDER_COLS.put(Customer.REGION, "customer.region");
SORT_COLS.put(Customer.SALESPERSON, "customer.salesperson"); ORDER_COLS.put(Customer.SALESPERSON, "customer.salesperson");
SORT_COLS.put("sumYtdSales", Customer.SUM_YTD_SALES); ORDER_COLS.put("sumYtdSales", Customer.SUM_YTD_SALES);
SORT_COLS.put("status", "prop.sort"); ORDER_COLS.put("status", "prop.sort");
SORT_COLS.put("application", "application_names"); ORDER_COLS.put("application", "application_names");
} }
private static final Map<String, String> CRITERION_COLS; private static final Map<String, String> CRITERION_COLS;
static { static {
CRITERION_COLS = new HashMap<>(SORT_COLS); CRITERION_COLS = new HashMap<>(ORDER_COLS);
CRITERION_COLS.put(Customer.NAME, "customer.name"); CRITERION_COLS.put(Customer.NAME, "customer.name");
CRITERION_COLS.put(Customer.STATE, "customer.state"); CRITERION_COLS.put(Customer.STATE, "customer.state");
CRITERION_COLS.put(Customer.CITY, "customer.city"); CRITERION_COLS.put(Customer.CITY, "customer.city");
@ -77,6 +76,48 @@ public class CustomerController
CRITERION_COLS.put("issue", "issue.artificial"); CRITERION_COLS.put("issue", "issue.artificial");
} }
// filters
private static final Map<String, Pair<String, Class<?>>> FILTER_COLS = new HashMap<>();
static {
FILTER_COLS.put("dateAdded",
Pair.of(CRITERION_COLS.get("dateAdded"), Integer.class));
FILTER_COLS.put("year",
Pair.of(CRITERION_COLS.get("year"), String.class));
FILTER_COLS.put("ms",
Pair.of(CRITERION_COLS.get("ms"), String.class));
FILTER_COLS.put("region",
Pair.of(CRITERION_COLS.get("region"), String.class));
FILTER_COLS.put("countryCode",
Pair.of(CRITERION_COLS.get("countryCode"), String.class));
FILTER_COLS.put("state",
Pair.of(CRITERION_COLS.get("state"), String.class));
FILTER_COLS.put("city",
Pair.of(CRITERION_COLS.get("city"), String.class));
FILTER_COLS.put("salesperson",
Pair.of(CRITERION_COLS.get("city"), String.class));
FILTER_COLS.put("status",
Pair.of(CRITERION_COLS.get("status"), String.class));
FILTER_COLS.put("application",
Pair.of(CRITERION_COLS.get("application"), String.class));
FILTER_COLS.put("issue",
Pair.of(CRITERION_COLS.get("issue"), Boolean.class));
// filter(search, joFilters, "dateAdded", Integer[].class);
// filter(search, joFilters, "year");
//
// filter(search, joFilters, "ms");
// filter(search, joFilters, "region");
// filter(search, joFilters, "countryCode");
// filter(search, joFilters, "state");
// filter(search, joFilters, "city");
// filter(search, joFilters, "salesperson");
// filter(search, joFilters, "status");
// filter(search, joFilters, "application");
//
// filter(search, joFilters, "issue", Boolean[].class);
}
@RequestMapping("/list") @RequestMapping("/list")
public ListResult<ViewModel<Customer>> list( public ListResult<ViewModel<Customer>> list(
@Min(0) @Min(0)
@ -92,10 +133,10 @@ public class CustomerController
final String criteria, final String criteria,
@RequestParam(value = "filters", required = false) @RequestParam(value = "filters", required = false)
final String filters, final String filters,
@RequestParam(value = "sorters", required = false) @RequestParam(value = "orders", required = false)
final String sorters) { final String orders) {
final Search search = filters(new Search(), filters); final Search search = new Search();
User user = getUser().getData(); User user = getUser().getData();
// if (!user.isAdmin()) { // if (!user.isAdmin()) {
search.setAttr(User.ACCOUNT, user.getAccount()); search.setAttr(User.ACCOUNT, user.getAccount());
@ -104,9 +145,7 @@ public class CustomerController
if (StringUtils.isNotBlank(strSearch)) { if (StringUtils.isNotBlank(strSearch)) {
search.setAttr("YTD_SALE", true); search.setAttr("YTD_SALE", true);
} }
final ListResult<ViewModel<Customer>> result = return listViewModels(search, start, limit, strSearch, criteria, filters, orders);
listViewModels(search(criteria(search, criteria), start, limit, strSearch, sorters));
return result;
} }
@RequiresRoles(User.ROLE_ADMIN) @RequiresRoles(User.ROLE_ADMIN)
@ -135,7 +174,7 @@ public class CustomerController
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
protected String[] searchCols() { protected List<String> searchCols() {
return SEARCH_COLS; return SEARCH_COLS;
} }
@ -143,174 +182,42 @@ public class CustomerController
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
protected Map<String, String> sorterCols() { protected Pair<String, Class<?>> filterCol(final String field) {
return SORT_COLS; return FILTER_COLS.get(field);
} }
private String criterionCol(String col) { /**
return CRITERION_COLS.get(col); * {@inheritDoc}
*/
protected Pair<String, Class<?>> criterionCol(final String col) {
return Pair.of(CRITERION_COLS.get(col), Object.class);
} }
private Search criteria(final Search search, final String strCriteria) { /**
* {@inheritDoc}
if (StringUtils.isBlank(strCriteria)) { */
log.debug("No Criteria Given."); @Override
return search; protected String orderCol(final String field) {
return ORDER_COLS.get(field);
} }
final List<String[]> criteria; /**
try { * {@inheritDoc}
criteria = JSON.parseArray(decodeParam(strCriteria), String[].class); */
} @Override
catch (Exception e) { protected Search defaultFilter(final Search search) {
throw new IllegalStateException( return search.ne(CRITERION_COLS.get("status"), CustomerProperty.STATUS_NA_ID);
"Invalid Criteria [" + strCriteria + "]", e);
} }
for (String[] criterion : criteria) { /**
if (criterion.length == 3) { * {@inheritDoc}
final String col = criterion[0]; */
if (StringUtils.isNotBlank(col)) { @Override
final String criterionCol = criterionCol(col); protected void onSearch(final Search search, final String col) {
if (StringUtils.isNotBlank(criterionCol)) {
// for search year // for search year
if (CustomerYearToDateSale.YEAR.equals(criterionCol) || if (CustomerYearToDateSale.YEAR.equals(col) ||
CustomerYearToDateSale.YTD_SALE.equals(criterionCol)) { CustomerYearToDateSale.YTD_SALE.equals(col)) {
search.setAttr("YTD_SALE", true); search.setAttr("YTD_SALE", true);
} }
final String op = criterion[1];
final String val = criterion[2];
if (StringUtils.isNotBlank(op) && StringUtils.isNotBlank(val)) {
if ("eq".equals(op)) {
search.eq(criterionCol, val);
}
else if ("gt".equals(op)) {
search.gt(criterionCol, val);
}
else if ("gte".equals(op)) {
search.gte(criterionCol, val);
}
else if ("lt".equals(op)) {
search.lt(criterionCol, val);
}
else if ("lte".equals(op)) {
search.lt(criterionCol, val);
}
else if ("ne".equals(op)) {
search.ne(criterionCol, val);
}
else if ("like".equals(op)) {
search.like(criterionCol, val, true);
}
else {
log.warn("Unknown Criterion Op [{}], Ignore.", op);
}
}
}
}
else {
log.warn("Criterion Col Is Blank, Ignore.");
}
}
else {
log.warn("Invalid Criterion [{}], Ignore.", criterion);
}
}
return search;
}
private Search filters(final Search search, final String strFilters) {
if (StringUtils.isBlank(strFilters)) {
log.debug("No filters given.");
defaultFilter(search, "status");
return search;
}
final JSONObject joFilters;
try {
joFilters = JSON.parseObject(decodeParam(strFilters));
}
catch (Exception e) {
throw new IllegalStateException(
"Invalid Filters [" + strFilters + "]", e);
}
filter(search, joFilters, "dateAdded", Integer[].class);
filter(search, joFilters, "year");
filter(search, joFilters, "ms");
filter(search, joFilters, "region");
filter(search, joFilters, "countryCode");
filter(search, joFilters, "state");
filter(search, joFilters, "city");
filter(search, joFilters, "salesperson");
filter(search, joFilters, "status");
filter(search, joFilters, "application");
filter(search, joFilters, "issue", Boolean[].class);
return search;
}
private Search filter(final Search search, final JSONObject joFilters, final String field) {
return filter(search, joFilters, field, String[].class);
}
private <T> Search filter(final Search search, final JSONObject joFilters, final String field, Class<T[]> clazz) {
final String col = CRITERION_COLS.get(field);
if (StringUtils.isNotBlank(col)) {
final T[] filters =
joFilters.getObject(field, clazz);
if (filters != null && filters.length > 0) {
log.info("Col [{}] filters [{}] found.", col, filters);
if ("application".equals(col)) {
search.setAttr("APPLICATIONS", filters);
}
else {
in(search, col, filters);
}
}
else {
defaultFilter(search, field);
}
// filter year
if (CustomerYearToDateSale.YEAR.equals(col)) {
search.setAttr("YTD_SALE", true);
}
}
return search;
}
private void defaultFilter(final Search search, final String field) {
// default no NA
if ("status".equals(field)) {
search.ne(CRITERION_COLS.get(field), CustomerProperty.STATUS_NA_ID);
}
}
private void in(final Search search, final String col, final Object[] values) {
final Set<Object> setValues = new HashSet<>(Arrays.asList(values));
final Iterator<Object> valIt = setValues.iterator();
if (setValues.size() == 1) {
final Object val = valIt.next();
search.eq(col, "null".equals(val) ? null : val);
}
else {
boolean nullValue = false;
while (valIt.hasNext()) {
final Object val = valIt.next();
if (val == null || "null".equals(val)) {
nullValue = true;
valIt.remove();
break;
}
}
if (nullValue) {
search.and(new Search(col, setValues).or(col, null));
}
else {
search.in(col, setValues);
}
}
} }
} }

View File

@ -1,5 +1,7 @@
package com.pudonghot.ambition.crm.controller; package com.pudonghot.ambition.crm.controller;
import java.util.List;
import java.util.Arrays;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.Max; import javax.validation.constraints.Max;
import me.chyxion.tigon.model.ViewModel; import me.chyxion.tigon.model.ViewModel;
@ -24,6 +26,11 @@ import com.pudonghot.ambition.crm.form.update.CustomerIssueFormForUpdate;
public class CustomerIssueController public class CustomerIssueController
extends BaseQueryController<CustomerIssue> { extends BaseQueryController<CustomerIssue> {
private static final List<String> SEARCH_COLS =
Arrays.asList(
CustomerIssue.ISSUE,
CustomerIssue.NOTE);
@RequestMapping("/list") @RequestMapping("/list")
public ListResult<ViewModel<CustomerIssue>> list( public ListResult<ViewModel<CustomerIssue>> list(
@Min(0) @Min(0)
@ -54,10 +61,7 @@ public class CustomerIssueController
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
protected String[] searchCols() { protected List<String> searchCols() {
return new String[] { return SEARCH_COLS;
CustomerIssue.ISSUE,
CustomerIssue.NOTE
};
} }
} }

View File

@ -21,6 +21,9 @@ import com.pudonghot.ambition.crm.service.CustomerPropertyService;
import com.pudonghot.ambition.crm.form.create.CustomerPropertyFormForCreate; import com.pudonghot.ambition.crm.form.create.CustomerPropertyFormForCreate;
import com.pudonghot.ambition.crm.form.update.CustomerPropertyFormForUpdate; import com.pudonghot.ambition.crm.form.update.CustomerPropertyFormForUpdate;
import java.util.Arrays;
import java.util.List;
/** /**
* @author Shaun Chyxion <br> * @author Shaun Chyxion <br>
* chyxion@163.com <br> * chyxion@163.com <br>
@ -31,6 +34,11 @@ import com.pudonghot.ambition.crm.form.update.CustomerPropertyFormForUpdate;
public class CustomerPropertyController public class CustomerPropertyController
extends BaseQueryController<CustomerProperty> { extends BaseQueryController<CustomerProperty> {
private static final List<String> SEARCH_COLS =
Arrays.asList(
CustomerProperty.NAME,
CustomerProperty.NOTE);
@RequestMapping("/list") @RequestMapping("/list")
public ListResult<ViewModel<CustomerProperty>> list( public ListResult<ViewModel<CustomerProperty>> list(
@Min(0) @Min(0)
@ -54,7 +62,7 @@ public class CustomerPropertyController
Boolean.parseBoolean(enabled)); Boolean.parseBoolean(enabled));
} }
((CustomerPropertyService) queryService).initSystemStatus(getUserId()); ((CustomerPropertyService) queryService).initSystemStatus(getUserId());
return listViewModels(search, start, limit, strSearch, null); return listViewModels(search, start, limit, strSearch, null, null, null);
} }
@RequestMapping("/app-list") @RequestMapping("/app-list")
@ -70,7 +78,7 @@ public class CustomerPropertyController
if (StringUtils.isNotBlank(enabled)) { if (StringUtils.isNotBlank(enabled)) {
search.eq(CustomerProperty.ENABLED, Boolean.parseBoolean(enabled)); search.eq(CustomerProperty.ENABLED, Boolean.parseBoolean(enabled));
} }
return listViewModels(search, null, null, strSearch, null); return listViewModels(search, null, null, strSearch, null, null, null);
} }
@RequiresRoles(User.ROLE_ADMIN) @RequiresRoles(User.ROLE_ADMIN)
@ -100,10 +108,7 @@ public class CustomerPropertyController
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
protected String[] searchCols() { protected List<String> searchCols() {
return new String[] { return SEARCH_COLS;
CustomerProperty.NAME,
CustomerProperty.NOTE
};
} }
} }

View File

@ -19,6 +19,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import com.pudonghot.ambition.crm.form.create.HomePageFormForCreate; import com.pudonghot.ambition.crm.form.create.HomePageFormForCreate;
import com.pudonghot.ambition.crm.form.update.HomePageFormForUpdate; import com.pudonghot.ambition.crm.form.update.HomePageFormForUpdate;
import java.util.Arrays;
import java.util.List;
/** /**
* @author Shaun Chyxion <br> * @author Shaun Chyxion <br>
* chyxion@163.com <br> * chyxion@163.com <br>
@ -29,10 +32,10 @@ import com.pudonghot.ambition.crm.form.update.HomePageFormForUpdate;
@RequestMapping("/home-page") @RequestMapping("/home-page")
public class HomePageController public class HomePageController
extends BaseQueryController<HomePage> { extends BaseQueryController<HomePage> {
private static final String[] SEARCH_COLS = new String[] { private static final List<String> SEARCH_COLS =
Arrays.asList(
HomePage.NAME, HomePage.NAME,
HomePage.NOTE HomePage.NOTE);
};
@RequestMapping("/list") @RequestMapping("/list")
@RequiresRoles(User.ROLE_ADMIN) @RequiresRoles(User.ROLE_ADMIN)
@ -47,7 +50,7 @@ public class HomePageController
@RequestParam(value = "search", required = false) @RequestParam(value = "search", required = false)
final String search) { final String search) {
return listViewModels(start, limit, search, null); return listViewModels(start, limit, search);
} }
@RequiresRoles(User.ROLE_ADMIN) @RequiresRoles(User.ROLE_ADMIN)
@ -85,7 +88,7 @@ public class HomePageController
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
protected String[] searchCols() { protected List<String> searchCols() {
return SEARCH_COLS; return SEARCH_COLS;
} }
} }

View File

@ -1,6 +1,7 @@
package com.pudonghot.ambition.crm.controller; package com.pudonghot.ambition.crm.controller;
import java.util.List; import java.util.List;
import java.util.Arrays;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.chyxion.tigon.model.ViewModel; import me.chyxion.tigon.model.ViewModel;
import javax.validation.constraints.Max; import javax.validation.constraints.Max;
@ -23,6 +24,14 @@ import org.springframework.web.bind.annotation.RequestMapping;
public class ImportRecordController public class ImportRecordController
extends BaseQueryController<ImportRecord> { extends BaseQueryController<ImportRecord> {
private final List<String> SEARCH_COLS =
Arrays.asList("r.type",
"r.note",
"u.name",
"u.en_name",
"u.account",
"u.employee_id" );
@RequestMapping("/list") @RequestMapping("/list")
@RequiresRoles(User.ROLE_ADMIN) @RequiresRoles(User.ROLE_ADMIN)
public ViewModel<List<ViewModel<ImportRecord>>> list( public ViewModel<List<ViewModel<ImportRecord>>> list(
@ -36,7 +45,7 @@ public class ImportRecordController
@RequestParam(value = "search", required = false) @RequestParam(value = "search", required = false)
final String search) { final String search) {
return listViewModels(search(start, limit, search, null) return listViewModels(search(start, limit, search)
.table("r") .table("r")
.desc(ImportRecord.DATE_CREATED)); .desc(ImportRecord.DATE_CREATED));
} }
@ -45,7 +54,8 @@ public class ImportRecordController
* {@inheritDoc} * {@inheritDoc}
* @return search cols * @return search cols
*/ */
protected String[] searchCols() { @Override
return new String[] { "r.type", "r.note", "u.name", "u.en_name", "u.account", "u.employee_id" }; protected List<String> searchCols() {
return SEARCH_COLS;
} }
} }

View File

@ -1,17 +1,20 @@
package com.pudonghot.ambition.crm.controller; package com.pudonghot.ambition.crm.controller;
import java.util.Map; import java.util.Map;
import java.util.List;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import javax.validation.Valid; import javax.validation.Valid;
import me.chyxion.tigon.mybatis.Search;
import javax.validation.constraints.Max; import javax.validation.constraints.Max;
import javax.validation.constraints.Min; import javax.validation.constraints.Min;
import me.chyxion.tigon.model.ViewModel; import me.chyxion.tigon.model.ViewModel;
import me.chyxion.tigon.model.ListResult; import me.chyxion.tigon.model.ListResult;
import org.apache.ibatis.annotations.Param;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import com.pudonghot.ambition.crm.model.User; import com.pudonghot.ambition.crm.model.User;
import org.apache.ibatis.annotations.Param;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.hibernate.validator.constraints.NotBlank;
import com.pudonghot.ambition.crm.service.UserService; import com.pudonghot.ambition.crm.service.UserService;
import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
@ -31,22 +34,22 @@ import com.pudonghot.ambition.crm.form.update.UserFormForUpdatePassword;
public class UserController public class UserController
extends BaseQueryController<User> { extends BaseQueryController<User> {
private static final Map<String, String> SORT_COLS = new HashMap<>(); private static final List<String> SEARCH_COLS = Arrays.asList(
private static final String[] SEARCH_COLS = new String[] {
User.NAME, User.NAME,
User.EN_NAME, User.EN_NAME,
User.ACCOUNT, User.ACCOUNT,
User.EMPLOYEE_ID, User.EMPLOYEE_ID,
User.MOBILE, // User.MOBILE,
User.EMAIL, // User.EMAIL,
User.NOTE User.NOTE);
};
private static final Map<String, String> ORDER_COLS = new HashMap<>();
static { static {
SORT_COLS.put(User.ACCOUNT, User.ACCOUNT); ORDER_COLS.put(User.ACCOUNT, User.ACCOUNT);
SORT_COLS.put("employeeId", User.EMPLOYEE_ID); ORDER_COLS.put("employeeId", User.EMPLOYEE_ID);
SORT_COLS.put(User.MOBILE, User.MOBILE); ORDER_COLS.put(User.MOBILE, User.MOBILE);
SORT_COLS.put("enName", User.EN_NAME); ORDER_COLS.put("enName", User.EN_NAME);
} }
@RequestMapping("/list") @RequestMapping("/list")
@ -61,9 +64,14 @@ public class UserController
final int limit, final int limit,
@RequestParam(value = "search", required = false) @RequestParam(value = "search", required = false)
final String search, final String search,
@RequestParam(value = "sorters", required = false) @RequestParam(value = "orders", required = false)
final String sorters) { final String orders) {
return listViewModels(start, limit, search, sorters); return listViewModels(start, limit, search, null, null, orders);
}
@RequestMapping("/list-for-select")
public List<ViewModel<User>> listForSelect() {
return queryService.listViewModels(new Search().asc(User.EMPLOYEE_ID));
} }
@RequiresRoles(User.ROLE_ADMIN) @RequiresRoles(User.ROLE_ADMIN)
@ -100,7 +108,7 @@ public class UserController
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
protected String[] searchCols() { protected List<String> searchCols() {
return SEARCH_COLS; return SEARCH_COLS;
} }
@ -108,7 +116,7 @@ public class UserController
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
protected Map<String, String> sorterCols() { protected String orderCol(final String field) {
return SORT_COLS; return ORDER_COLS.get(field);
} }
} }

View File

@ -35,6 +35,12 @@ import com.pudonghot.ambition.crm.form.update.WeekGoalFormForUpdate;
public class WeekGoalController public class WeekGoalController
extends BaseQueryController<WeekGoal> { extends BaseQueryController<WeekGoal> {
private static final List<String> SEARCH_COLS =
Arrays.asList("u.name",
"u.en_name",
"u.account",
"u.employee_id");
@RequestMapping("/list") @RequestMapping("/list")
public ViewModel<List<ViewModel<WeekGoal>>> list( public ViewModel<List<ViewModel<WeekGoal>>> list(
@Min(0) @Min(0)
@ -50,7 +56,7 @@ public class WeekGoalController
final String strSearch) { final String strSearch) {
final List<String> employeeIds = employeeIdsFilter(filters); final List<String> employeeIds = employeeIdsFilter(filters);
return addYearSum(((WeekGoalService) queryService).listJoinUser( return addYearSum(((WeekGoalService) queryService).listJoinUser(
listFilters(search(start, limit, strSearch, null), employeeIds)), employeeIds); listFilters(search(start, limit, strSearch), employeeIds)), employeeIds);
} }
@RequestMapping("/mine") @RequestMapping("/mine")
@ -116,8 +122,8 @@ public class WeekGoalController
* @return search cols * @return search cols
*/ */
@Override @Override
protected String[] searchCols() { protected List<String> searchCols() {
return new String[] { "u.name", "u.en_name", "u.account", "u.employee_id" }; return SEARCH_COLS;
} }
/** /**

View File

@ -4,6 +4,7 @@ import java.util.Date;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import me.chyxion.tigon.model.ViewModel; import me.chyxion.tigon.model.ViewModel;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.pudonghot.ambition.file.AmbitionFileApi; import com.pudonghot.ambition.file.AmbitionFileApi;
@ -45,12 +46,31 @@ public class ApplicationServiceSupport
final Date now = new Date(); final Date now = new Date();
application.setDateUpdated(now); application.setDateUpdated(now);
final MultipartFile[] images = form.getImages(); uploadImages(id, 0, form.getImages(), form.getImageTitles(), form.getCreatedBy());
mapper.insert(application);
return toViewModel(application);
}
/**
* {@inheritDoc}
*/
@Override
public ViewModel<Application> update(final ApplicationFormForUpdate form) {
final String id = form.getId();
final Application application = find(id);
Assert.state(application != null, "No application [" + id + "] found");
final String updatedBy = form.getUpdatedBy();
Assert.state(form.isAdmin() || application.getOwner().equals(updatedBy),
"No permission to update application");
uploadImages(id, imageMapper.nextSort(id), form.getImages(), form.getImageTitles(), updatedBy);
return update(form.copy(application));
}
private void uploadImages(final String id, int sort, final MultipartFile[] images, final String[] titles, final String createdBy) {
if (images != null) { if (images != null) {
final String createdBy = form.getCreatedBy(); final Date now = new Date();
int sort = 0;
final String imageFolder = imageFolder(id); final String imageFolder = imageFolder(id);
final String[] imageTitles = form.getImageTitles();
int i = 0; int i = 0;
for (final MultipartFile image : images) { for (final MultipartFile image : images) {
if (!image.isEmpty()) { if (!image.isEmpty()) {
@ -61,7 +81,8 @@ public class ApplicationServiceSupport
appImage.setId(imageId); appImage.setId(imageId);
appImage.setApplicationId(id); appImage.setApplicationId(id);
appImage.setSort(sort++); appImage.setSort(sort++);
appImage.setNote(imageTitles[i]); appImage.setEnabled(true);
appImage.setNote(titles[i]);
appImage.setCreatedBy(createdBy); appImage.setCreatedBy(createdBy);
appImage.setDateCreated(now); appImage.setDateCreated(now);
imageMapper.insert(appImage); imageMapper.insert(appImage);
@ -74,16 +95,6 @@ public class ApplicationServiceSupport
++i; ++i;
} }
} }
mapper.insert(application);
return toViewModel(application);
}
/**
* {@inheritDoc}
*/
@Override
public ViewModel<Application> update(final ApplicationFormForUpdate form) {
return null;
} }
private String imageFolder(final String appId) { private String imageFolder(final String appId) {

View File

@ -1,6 +1,8 @@
package com.pudonghot.ambition.crm.mapper; package com.pudonghot.ambition.crm.mapper;
import me.chyxion.tigon.mybatis.BaseMapper; import me.chyxion.tigon.mybatis.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.hibernate.validator.constraints.NotBlank;
import com.pudonghot.ambition.crm.model.ApplicationImage; import com.pudonghot.ambition.crm.model.ApplicationImage;
/** /**
@ -10,4 +12,10 @@ import com.pudonghot.ambition.crm.model.ApplicationImage;
*/ */
public interface ApplicationImageMapper extends BaseMapper<String, ApplicationImage> { public interface ApplicationImageMapper extends BaseMapper<String, ApplicationImage> {
/**
* find next sort
* @param applicationId application id
* @return next sort
*/
int nextSort(@NotBlank @Param("applicationId") String applicationId);
} }

View File

@ -10,4 +10,11 @@
"-//mybatis.org//DTD Mapper 3.0//EN" "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pudonghot.ambition.crm.mapper.ApplicationImageMapper"> <mapper namespace="com.pudonghot.ambition.crm.mapper.ApplicationImageMapper">
<select id="nextSort" resultType="int">
select if (application_id, max(sort) + 1, 0)
from <include refid="table" />
where application_id = #{applicationId}
</select>
</mapper> </mapper>

View File

@ -10,4 +10,22 @@
"-//mybatis.org//DTD Mapper 3.0//EN" "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pudonghot.ambition.crm.mapper.ApplicationMapper"> <mapper namespace="com.pudonghot.ambition.crm.mapper.ApplicationMapper">
<select id="list" resultType="com.pudonghot.ambition.crm.model.Application">
select
<include refid="cols" />,
(select group_concat(id order by sort separator 0x1d) from
crm_application_image
where application_id = a.id
group by application_id) images,
(select group_concat(note order by sort separator 0x1d) from
crm_application_image
where application_id = a.id
group by application_id) image_titles
from
<include refid="table" /> a
<include refid="Tigon.search" />
</select>
</mapper> </mapper>

View File

@ -21,6 +21,8 @@ import org.springframework.web.multipart.MultipartFile;
public class ApplicationFormForUpdate extends FU2<String, String> { public class ApplicationFormForUpdate extends FU2<String, String> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
// current user is admin
private boolean admin;
@NotBlank @NotBlank
@Length(max = 64) @Length(max = 64)
private String name; private String name;

View File

@ -4,6 +4,7 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import me.chyxion.tigon.model.M3; import me.chyxion.tigon.model.M3;
import me.chyxion.tigon.mybatis.Table; import me.chyxion.tigon.mybatis.Table;
import me.chyxion.tigon.mybatis.Transient;
/** /**
* @author Donghuang <br> * @author Donghuang <br>
@ -25,4 +26,10 @@ public class Application extends M3<String, String> {
private String name; private String name;
private String content; private String content;
private String owner; private String owner;
// Transient Props
@Transient
private String images;
@Transient
private String imageTitles;
} }

View File

@ -0,0 +1,21 @@
import Component from '@ember/component';
import { htmlSafe } from '@ember/string';
export default Component.extend({
tagName: '',
didReceiveAttrs() {
const me = this;
me._super(...arguments);
me.set('content', htmlSafe(me.get('model.content')));
},
actions: {
show() {
let me = this;
me.set('show', true);
},
close() {
let me = this;
me.set('show', false);
}
}
});

View File

@ -32,6 +32,7 @@ export default BaseFormInput.extend({
allowClear: me.get('nullable'), allowClear: me.get('nullable'),
placeholder: me.get('placeholder') || me.get('label') || 'Please select...' placeholder: me.get('placeholder') || me.get('label') || 'Please select...'
}).on('change', function(e) { }).on('change', function(e) {
console.log('select2 change: ', e);
if (e.removed) { if (e.removed) {
me.unselect(me.findOption(e.removed.id)); me.unselect(me.findOption(e.removed.id));
} }
@ -64,7 +65,7 @@ export default BaseFormInput.extend({
if (me.get('multiple')) { if (me.get('multiple')) {
const vals = me.getVal(); const vals = me.getVal();
if (isArray(vals)) { if (isArray(vals)) {
vals.forEach(v => me.select(me.findOption(v))); vals.each(v => me.select(me.findOption(v)));
} }
else { else {
me.setVal(me.getSelected()); me.setVal(me.getSelected());
@ -101,6 +102,6 @@ export default BaseFormInput.extend({
getSelected() { getSelected() {
const me = this; const me = this;
const selected = me.get('options').filter(o => o.selected).mapBy(me.get('value-field')); const selected = me.get('options').filter(o => o.selected).mapBy(me.get('value-field'));
return me.get('multiple') ? selected : selected[0]; return selected.length > 1 ? selected : selected.length == 1 ? selected[0] : null;
} }
}); });

View File

@ -1,18 +1,25 @@
import Ember from 'ember'; import Ember from 'ember';
import Component from '@ember/component';
import { computed } from '@ember/object';
import $ from 'jquery'
export default Ember.Component.extend({ export default Component.extend({
tagName: 'span', tagName: 'span',
classNames: ['inline'], classNames: ['inline'],
index: 0,
images: computed.alias('previews'),
'image-height': 22,
imageHeight: computed.alias('image-height'),
'image-style': 'border-radius: 8%; border: 1px solid #DCDCDC; max-width: 32px;',
imageStyle: computed.alias('image-style'),
didReceiveAttrs() { didReceiveAttrs() {
let me = this; let me = this;
let previews = me.get('previews'); let images = me.get('images');
if (Ember.$.type(previews) === 'string') { if (Ember.$.type(images) === 'string') {
let sep = me.get('separator'); let sep = me.get('separator');
me.set('previews', sep ? previews.split(sep) : [previews]); me.set('images', sep ? images.split(sep) : [images]);
} }
}, },
imageHeight: 22,
imageStyle: 'border-radius: 8%; border: 1px solid #DCDCDC; max-width: 32px;',
didInsertElement() { didInsertElement() {
let me = this; let me = this;
me._super(...arguments); me._super(...arguments);

View File

@ -1,16 +1,22 @@
import Ember from 'ember'; import Component from '@ember/component'
import BaseComponentMixin from '../mixins/components/base-component'; import BaseComponentMixin from '../mixins/components/base-component';
export default Ember.Component.extend(BaseComponentMixin, { export default Component.extend(BaseComponentMixin, {
classNames: ['modal', 'fade'], classNames: ['modal', 'fade'],
'init-modal': true, 'init-modal': true,
transitionToParentRouteAfterClose: true, 'close-to-parent': true,
'cancel-text': 'Cancel',
'submit-text': 'Submit',
keyboard: true,
didInsertElement() { didInsertElement() {
let me = this; let me = this;
me._super(...arguments); me._super(...arguments);
if (me.get('init-modal')) { if (me.get('init-modal')) {
me.$().modal().on('hidden.bs.modal', ()=> { me.$().modal({
me.$() && me.get('transitionToParentRouteAfterClose') && backdrop: me.get('backdrop'),
keyboard: me.get('keyboard')
}).on('hidden.bs.modal', ()=> {
me.$() && me.get('close-to-parent') &&
me.get('router').transitionTo( me.get('router').transitionTo(
me.get('parentRouteName') || me.get('parentRouteName') ||
me.get('routeName').replace(/\.[^.]+$/, '')); me.get('routeName').replace(/\.[^.]+$/, ''));

View File

@ -10,7 +10,8 @@ const OptionTextComponent = Component.extend({
me._super(...arguments); me._super(...arguments);
let val = me.get('value'); let val = me.get('value');
if (val) { if (val) {
const option = (me.get('options') || []).findBy(me.get('value-field'), val); const valueField = me.get('value-field');
const option = (me.get('options') || []).findBy(valueField, val);
if (option) { if (option) {
let text = option[me.get('text-field')]; let text = option[me.get('text-field')];
let textExp = me.get('text-exp'); let textExp = me.get('text-exp');

View File

@ -3,7 +3,7 @@ import BaseComponentMixin from '../mixins/components/base-component';
export default Ember.Component.extend(BaseComponentMixin, { export default Ember.Component.extend(BaseComponentMixin, {
tagName: 'th', tagName: 'th',
order: Ember.computed('route.controller.sorters', function() { order: Ember.computed('route.controller.orders', function() {
return this.getDir(); return this.getDir();
}), }),
sorting: Ember.computed.none('order'), sorting: Ember.computed.none('order'),
@ -14,44 +14,44 @@ export default Ember.Component.extend(BaseComponentMixin, {
getDir() { getDir() {
let me = this; let me = this;
let name = me.get('name'); let name = me.get('name');
let sorters = me.getSorters(); let orders = me.getOrders();
if (sorters && sorters.length) { if (orders && orders.length) {
let sorter = sorters.find(sorter => sorter.hasOwnProperty(name)); let order = orders.find(order => order.hasOwnProperty(name));
if (sorter) { if (order) {
return sorter[name]; return order[name];
} }
} }
}, },
getSorters() { getOrders() {
let me = this; let me = this;
let strSorters = me.get('route.controller.sorters'); let strOrders = me.get('route.controller.orders');
if (strSorters) { if (strOrders) {
return JSON.parse(strSorters); return JSON.parse(strOrders);
} }
}, },
actions: { actions: {
sort() { sort() {
let me = this; let me = this;
let sorters = me.getSorters() || []; let orders = me.getOrders() || [];
let name = me.get('name'); let name = me.get('name');
if (sorters.length) { if (orders.length) {
sorters = sorters.filter(sorter => !sorter.hasOwnProperty(name)); orders = orders.filter(order => !order.hasOwnProperty(name));
} }
let sorter = {}; let order = {};
sorter[name] = order[name] =
me.get('order') === 'asc' ? me.get('order') === 'asc' ?
'desc' : 'asc'; 'desc' : 'asc';
// prepend sort // prepend sort
sorters.unshift(sorter); orders.unshift(order);
me.set('route.controller.sorters', JSON.stringify(sorters)); me.set('route.controller.orders', JSON.stringify(orders));
}, },
removeSort() { removeSort() {
let me = this; let me = this;
let sorters = me.getSorters(); let orders = me.getOrders();
if (sorters) { if (orders) {
let name = me.get('name'); let name = me.get('name');
me.set('route.controller.sorters', me.set('route.controller.orders',
JSON.stringify(sorters.filter(sorter => !sorter.hasOwnProperty(name)))); JSON.stringify(orders.filter(order => !order.hasOwnProperty(name))));
} }
} }
} }

View File

@ -0,0 +1,16 @@
import { helper } from '@ember/component/helper';
export function customerApplicationImages([fileBasePath, appId, images, titles]) {
if (images) {
titles = titles ? titles.split(String.fromCharCode(0x1d)) : [];
return images.split(String.fromCharCode(0x1d)).map((image, i) => {
return {
image: fileBasePath + 'app/' + appId + '/' + image,
title: titles[i]
}
});
}
return [];
}
export default helper(customerApplicationImages);

View File

@ -6,7 +6,10 @@ export default BaseRoute.extend({
search: { search: {
refreshModel: true refreshModel: true
}, },
sorters: { filters: {
refreshModel: true
},
orders: {
refreshModel: true refreshModel: true
} }
}, },

View File

@ -1,6 +1,7 @@
import Ember from 'ember'; import Ember from 'ember';
import BaseRoute from '../base'; import BaseRoute from '../base';
import EmberObject, { computed } from '@ember/object'; import EmberObject, { computed } from '@ember/object';
import RSVP from 'rsvp';
export default BaseRoute.extend({ export default BaseRoute.extend({
breadcrumbs: [{route: 'customer-application.list', params: 1, text: 'Customer Application'}, breadcrumbs: [{route: 'customer-application.list', params: 1, text: 'Customer Application'},
@ -11,9 +12,10 @@ export default BaseRoute.extend({
}) })
}), }),
model() { model() {
return this.get('modelClass').create({ return RSVP.hash({
enabled: true, enabled: true,
images: [{}] images: [{}],
users: this.get('store').ajaxGet('user/list-for-select')
}); });
}, },
actions: { actions: {

View File

@ -2,10 +2,23 @@ import Ember from 'ember';
import BaseEditRoute from '../base-edit'; import BaseEditRoute from '../base-edit';
export default BaseEditRoute.extend({ export default BaseEditRoute.extend({
service: Ember.inject.service('customer-property.service'),
afterModel(model) { afterModel(model) {
const me = this;
me._super(...arguments);
model.images = [{}];
this.set('breadcrumbs', this.set('breadcrumbs',
[{route: 'customer-application.list', params: 1, text: 'Customer Application'}, [{route: 'customer-application.list', params: 1, text: 'Customer Application'},
{text: 'Edit Customer Application[' + model.name + ']'}]); {text: 'Edit Customer Application[' + model.name + ']'}]);
},
actions: {
addImage() {
const me = this;
me.get('controller.model.images').pushObject({});
},
removeImage(image) {
const me = this;
me.get('controller.model.images').removeObject(image);
},
} }
}); });

View File

@ -6,4 +6,18 @@ export default BaseListRoute.extend({
// model(params) { // model(params) {
// return this.get('store').ajaxGet('customer-property/app-list', params); // return this.get('store').ajaxGet('customer-property/app-list', params);
// } // }
actions: {
showContent(app) {
this.get('dialog').dialog({
title: app.name,
message: `<p>${app.content}</p>`,
buttons: {
ok: {
label: 'OK',
className: 'btn-info'
}
}
});
}
}
}); });

View File

@ -65,9 +65,6 @@ export default BaseListRoute.extend({
queryParams: { queryParams: {
criteria: { criteria: {
refreshModel: true refreshModel: true
},
filters: {
refreshModel: true
} }
}, },
breadcrumbs: [{text: 'Customers'}], breadcrumbs: [{text: 'Customers'}],

View File

@ -0,0 +1,18 @@
<button {{action 'show'}} class="btn btn-minier btn-info2" data-rel="tooltip" title="Content Preview">
<i class="fa fa-search" aria-hidden="true"></i>
</button>
{{#if show}}
{{#modal-dialog title=model.name no-cancel=true submit=(action 'close') on-close=(action 'close') close-to-parent=false}}
<div class="widget-main">
<div class="space-12"></div>
<p>{{content}}</p>
<hr />
{{#with (customer-application-images file-base-path model.id model.images model.imageTitles) as |images|}}
{{#each images as |image index|}}
{{image-previews cover-title=true image-height=192 image-style='border-radius: 2%; border: 1px solid #DCDCDC; max-width: 480px;' previews=images index=index}}
{{/each}}
{{/with}}
</div>
{{/modal-dialog}}
{{/if}}

View File

@ -1,12 +1,23 @@
{{#if previews}} {{#if images}}
<a href="{{previews.firstObject}}" data-rel="{{elementId}}_preview"> <ul class="ace-thumbnails clearfix0">
<img height="{{imageHeight}}" style="{{imageStyle}}" src="{{previews.firstObject}}" /> {{#each images as |image i|}}
</a> {{#if (eq i index)}}
<div class="dropdown-menu"> <li class="no-margin no-padding clearfix" style="float: inherit; overflow: inherit; border-width: 0">
{{#each previews as |preview index|}} <a href="{{if image.image image.image iamge}}"
{{#if (gt index 0)}} title={{image.title}} data-rel="{{elementId}}_preview">
<a href="{{preview}}" data-rel="{{elementId}}_preview"></a> <img height="{{image-height}}" style="{{image-style}}"
src="{{if image.image image.image image}}"
title={{image.title}} />
{{#if (and cover-title image.title)}}
<div class="text">
<div class="inner">{{image.title}}</div>
</div>
{{/if}}
</a>
</li>
{{else}}
<a style="display: none" href="{{if image.image image.image image}}" data-rel="{{elementId}}_preview" title={{image.title}}></a>
{{/if}} {{/if}}
{{/each}} {{/each}}
</div> </ul>
{{/if}} {{/if}}

View File

@ -1,25 +1,33 @@
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header" style="padding: 9px;"> <div class="modal-header" style="padding: 9px;">
{{#if (not (get this 'no-close'))}}
<button type="button" class="bootbox-close-button close" data-dismiss="modal" aria-hidden="true"> <button type="button" class="bootbox-close-button close" data-dismiss="modal" aria-hidden="true">
&times; &times;
</button> </button>
{{/if}}
<h4 class="blue">{{title}}</h4> <h4 class="blue">{{title}}</h4>
</div> </div>
<div class="modal-body no-padding"> <div class="modal-body no-padding">
{{yield}} {{yield}}
</div> </div>
{{#if (not (get this 'no-footer'))}}
<div class="modal-footer no-margin-top"> <div class="modal-footer no-margin-top">
{{#if (not (get this 'no-cancel'))}}
<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal"> <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">
<i class="ace-icon fa fa-times"></i> <i class="ace-icon fa fa-times"></i>
Cancel {{cancel-text}}
</button> </button>
<button type="button" class="btn btn-sm btn-primary" {{action (if submit submit (route-action 'submit'))}}> {{/if}}
{{#if (not (get this 'no-submit'))}}
<button type="button" class="btn btn-sm btn-primary"
{{action (if submit submit (route-action 'submit'))}}>
<i class="ace-icon fa fa-check"></i> <i class="ace-icon fa fa-check"></i>
OK {{submit-text}}
</button> </button>
{{/if}}
</div> </div>
{{/if}}
</div> </div>
</div> </div>

View File

@ -11,8 +11,9 @@
col-width=12 col-width=12
label=(if label label text) label=(if label label text)
options=options options=options
value-field=(get this 'value-field') value-field=value-field
text-field=(get this 'text-field') text-field=text-field
text-exp=text-exp
}} }}
</form> </form>
</div> </div>

View File

@ -1,5 +1,4 @@
{{#form-content}} {{#form-content}}
{{form-input type='hidden' name='type'}}
{{form-input name='name' label='Name'}} {{form-input name='name' label='Name'}}
{{#form-input name='content' label='Content'}} {{#form-input name='content' label='Content'}}
{{wysiwyg-editor model=model name='content'}} {{wysiwyg-editor model=model name='content'}}
@ -16,7 +15,6 @@
</div> </div>
<div class="widget-body"> <div class="widget-body">
<div class="widget-main padding-4"> <div class="widget-main padding-4">
<!-- #section:pages/profile.feed -->
{{#each model.images as |image i|}} {{#each model.images as |image i|}}
<div class="col-xs-6 no-padding-left"> <div class="col-xs-6 no-padding-left">
<div class="space-4"></div> <div class="space-4"></div>
@ -29,12 +27,20 @@
{{image-input name=(concat 'images[' i ']') image=image}} {{image-input name=(concat 'images[' i ']') image=image}}
</div> </div>
{{/each}} {{/each}}
<!-- /section:pages/profile.feed -->
</div> </div>
</div> </div>
</div> </div>
{{/form-input}} {{/form-input}}
{{form-input name='note' label='Remark'}} {{form-input name='note' label='Remark'}}
{{form-input-select2
nullable=true
name='owner'
label='Owner'
options=model.users
value-field='id'
text-field='name'
text-exp='$.employeeId ($.name)'
}}
<hr /> <hr />
{{form-footer-buttons}} {{form-footer-buttons}}
{{/form-content}} {{/form-content}}

View File

@ -1,7 +1,39 @@
{{#form-content}} {{#form-content}}
{{input type='hidden' name='id' value=model.id}} {{input type='hidden' name='id' value=model.id}}
{{form-input type='hidden' name='type'}}
{{form-input name='name' label='Name'}} {{form-input name='name' label='Name'}}
{{#form-input name='content' label='Content'}}
{{wysiwyg-editor model=model name='content'}}
{{/form-input}}
{{#form-input name='image' label='Images'}}
<div class="widget-box transparent">
<div class="widget-header widget-header-small">
<div class="widget-toolbar action-buttons">
<a href="#" data-rel="tooltip" title="Add Image" {{action (route-action 'addImage')}}>
<i class="ace-icon fa fa-plus-circle green"></i>
</a>
</div>
</div>
<div class="widget-body">
<div class="widget-main padding-4">
{{#each model.images as |image i|}}
<div class="col-xs-6 no-padding-left">
<div class="space-4"></div>
{{input name=(concat 'imageTitles[' i ']') class='width-80' placeholder='Image title' value=image.title}}
&nbsp;
<a href="#" class="red" data-rel="tooltip" title="Remove Image" {{action (route-action 'removeImage' image)}}>
<i class="ace-icon fa fa-times bigger-125"></i>
</a>
<div class="space-2"></div>
{{image-input name=(concat 'images[' i ']') image=image}}
</div>
{{/each}}
</div>
</div>
</div>
{{/form-input}}
{{form-input-enabled label='Enabled' enabledText='TRUE' disabledText='FALSE'}} {{form-input-enabled label='Enabled' enabledText='TRUE' disabledText='FALSE'}}
{{form-input name='note' label='Remark'}} {{form-input name='note' label='Remark'}}
<hr /> <hr />

View File

@ -22,20 +22,33 @@
<th> <th>
Name Name
</th> </th>
<th>
Content
</th>
<th> <th>
<i class="ace-icon fa fa-sticky-note-o bigger-110 hidden-480"></i> <i class="ace-icon fa fa-sticky-note-o bigger-110 hidden-480"></i>
Remark Remark
</th> </th>
{{#if ajax.user.admin}}
<th>
<i class="ace-icon fa fa-id-badge bigger-110 hidden-480"></i>
{{th-filter name='owner'
text='Owner'
label='Owner Filter'
options=model.users
value-field='id'
text-exp='$.employeeId ($.name)'
}}
</th>
{{/if}}
<th> <th>
<i class="ace-icon fa fa-exchange bigger-110 hidden-480"></i> <i class="ace-icon fa fa-exchange bigger-110 hidden-480"></i>
Enabled Enabled
</th> </th>
{{#if ajax.user.admin}}
<th> <th>
<i class="ace-icon fa fa-cogs bigger-110 hidden-480"></i> <i class="ace-icon fa fa-cogs bigger-110 hidden-480"></i>
Settings Settings
</th> </th>
{{/if}}
</tr> </tr>
</thead> </thead>
@ -46,16 +59,20 @@
{{it.name}} {{it.name}}
</td> </td>
<td> <td>
{{#if ajax.user.admin}} {{image-previews previews=(customer-application-images model.fileBasePath it.id it.images it.imageTitles)}}
{{editable-cell model=it field='note'}} {{customer-application/preview-btn model=it file-base-path=model.fileBasePath}}
{{else}}
{{it.note}}
{{/if}}
</td> </td>
<td>
{{editable-cell model=it field='note'}}
</td>
{{#if ajax.user.admin}}
<td>
{{option-text model.users it.owner 'id' 'name' '$.employeeId ($.name)'}}
</td>
{{/if}}
<td> <td>
{{status-cell model=it enabledText='TRUE' disabledText='FALSE'}} {{status-cell model=it enabledText='TRUE' disabledText='FALSE'}}
</td> </td>
{{#if ajax.user.admin}}
<td> <td>
<div class="hidden-sm hidden-xs btn-group"> <div class="hidden-sm hidden-xs btn-group">
{{status-toggle-button model=it}} {{status-toggle-button model=it}}
@ -111,7 +128,6 @@
</div> </div>
</div> </div>
</td> </td>
{{/if}}
</tr> </tr>
{{/each}} {{/each}}
</tbody> </tbody>

View File

@ -0,0 +1,24 @@
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
moduleForComponent('customer-application/preview-btn', 'Integration | Component | customer application/preview btn', {
integration: true
});
test('it renders', function(assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{customer-application/preview-btn}}`);
assert.equal(this.$().text().trim(), '');
// Template block usage:
this.render(hbs`
{{#customer-application/preview-btn}}
template block text
{{/customer-application/preview-btn}}
`);
assert.equal(this.$().text().trim(), 'template block text');
});

View File

@ -0,0 +1,17 @@
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
moduleForComponent('customer-application-images', 'helper:customer-application-images', {
integration: true
});
// Replace this with your real tests.
test('it renders', function(assert) {
this.set('inputValue', '1234');
this.render(hbs`{{customer-application-images inputValue}}`);
assert.equal(this.$().text().trim(), '1234');
});