add state, #IHVSN

This commit is contained in:
东皇 2018-02-12 20:23:03 +08:00
parent a5a86ba38c
commit 04c3843c9f
65 changed files with 1819 additions and 395 deletions

View File

@ -5,6 +5,7 @@ import java.util.List;
import java.util.HashMap;
import javax.validation.Valid;
import com.alibaba.fastjson.JSON;
import com.pudonghot.ambition.crm.model.CustomerProperty;
import lombok.extern.slf4j.Slf4j;
import com.alibaba.fastjson.JSONObject;
import me.chyxion.tigon.mybatis.Search;
@ -221,6 +222,7 @@ public class CustomerController
if (StringUtils.isBlank(strFilters)) {
log.debug("No filters given.");
defaultFilter(search, "status");
return search;
}
@ -259,6 +261,9 @@ public class CustomerController
log.info("Col [{}] filters [{}] found.", col, filters);
search.in(col, filters);
}
else {
defaultFilter(search, field);
}
// filter year
if (CustomerYearToDateSale.YEAR.equals(col)) {
@ -267,4 +272,11 @@ public class CustomerController
}
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);
}
}
}

View File

@ -1,5 +1,6 @@
package com.pudonghot.ambition.crm.controller;
import java.util.List;
import javax.validation.Valid;
import com.alibaba.fastjson.JSONArray;
import me.chyxion.tigon.mybatis.Search;
@ -7,11 +8,11 @@ import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import me.chyxion.tigon.model.ViewModel;
import me.chyxion.tigon.model.ListResult;
import javax.validation.constraints.NotNull;
import com.pudonghot.ambition.crm.model.User;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.NotBlank;
import org.apache.shiro.authz.annotation.RequiresRoles;
import com.pudonghot.ambition.crm.model.CustomerProperty;
import org.springframework.web.bind.annotation.RequestParam;
@ -40,9 +41,9 @@ public class CustomerPropertyController
@Max(512)
@RequestParam(value = "limit", defaultValue = "16")
final int limit,
@NotBlank
@NotNull
@RequestParam("type")
final String type,
final CustomerProperty.Type type,
@RequestParam(value = "enabled", required = false)
final String enabled,
@RequestParam(value = "search", required = false)
@ -57,6 +58,22 @@ public class CustomerPropertyController
return listViewModels(search, start, limit, strSearch, null);
}
@RequestMapping("/list-for-select")
public List<ViewModel<CustomerProperty>> listForSelect(
@NotNull
@RequestParam("type")
final CustomerProperty.Type type,
@RequestParam(value = "enabled", required = false)
final String enabled) {
final Search search = new Search(CustomerProperty.TYPE, type)
.asc(CustomerProperty.SORT);
if (StringUtils.isNotBlank(enabled)) {
search.eq(CustomerProperty.ENABLED,
Boolean.parseBoolean(enabled));
}
return queryService.listViewModels(search);
}
@RequiresRoles(User.ROLE_ADMIN)
@RequestMapping(value = "/create", method = RequestMethod.POST)
public ViewModel<CustomerProperty> create(

View File

@ -80,7 +80,7 @@ public class CustomerPropertyServiceSupport
final CustomerProperty status = new CustomerProperty();
status.setId(id);
status.setName(name);
status.setType(CustomerProperty.TYPE_STATUS);
status.setType(CustomerProperty.Type.STATUS);
status.setEnabled(true);
status.setNote(note);
status.setCreatedBy(operator);

View File

@ -2,6 +2,8 @@ package com.pudonghot.ambition.crm.service.support;
import java.util.*;
import java.io.InputStream;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import me.chyxion.tigon.mybatis.Search;
import me.chyxion.tigon.model.ViewModel;
@ -62,14 +64,26 @@ public class CustomerServiceSupport
// accounts
final List<CustomerPermission> permissions = customerPermissionMapper.list(
new Search(CustomerPermission.CUSTOMER_ID, id)
.eq(CustomerPermission.ENABLED, true));
new Search(CustomerPermission.CUSTOMER_ID, id)
.eq(CustomerPermission.ENABLED, true));
final List<String> accounts = new ArrayList<>(permissions.size());
for (CustomerPermission permission : permissions) {
accounts.add(permission.getUserAccount());
}
viewModel.setAttr("users", userService.listByCustomerId(id));
viewModel.setAttr("issues", customerIssueService.listRecent(id, 128));
viewModel.setAttr("applications", customerPropertyMapper.listApplication(id)
.stream().map(CustomerProperty::getId).collect(Collectors.toList()));
viewModel.setAttr("statusList", customerPropertyMapper.list(
new Search(CustomerProperty.TYPE,
CustomerProperty.Type.STATUS)
.eq(CustomerProperty.ENABLED, true)
.asc(CustomerProperty.SORT)));
viewModel.setAttr("applicationList", customerPropertyMapper.list(
new Search(CustomerProperty.TYPE,
CustomerProperty.Type.APPLICATION)
.eq(CustomerProperty.ENABLED, true)
.asc(CustomerProperty.SORT)));
}
return viewModel;
}
@ -95,7 +109,7 @@ public class CustomerServiceSupport
result.setAttr("salespersonList", mapper.listSalesperson());
result.setAttr("statusList",
customerPropertyMapper.listForFilter(CustomerProperty.TYPE_STATUS));
customerPropertyMapper.listForFilter(CustomerProperty.Type.STATUS));
return result;
}

View File

@ -4,8 +4,8 @@ server.port=8088
# MySQL
datasource.host=127.0.0.1
datasource.port=43306
datasource.database-name=ambition_crm
datasource.port=63306
datasource.database-name=ambition_crm_test
datasource.username=root
datasource.password=696@2^~)oZ@^#*Q

View File

@ -101,6 +101,18 @@
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<filtering>false</filtering>
<includes>
<include>**/**Mapper.xml</include>
</includes>
</resource>
</resources>
</build>
<profiles>
<profile>
<id>dev</id>

View File

@ -1,13 +1,13 @@
package com.pudonghot.ambition.crm.mapper;
import java.util.Map;
import java.util.List;
import me.chyxion.tigon.mybatis.BaseMapper;
import org.apache.ibatis.annotations.Param;
import javax.validation.constraints.NotNull;
import com.pudonghot.ambition.crm.model.CustomerProperty;
import org.hibernate.validator.constraints.NotBlank;
import java.util.List;
import java.util.Map;
/**
* @version 0.0.1
* @author Auto Generated <br>
@ -16,15 +16,29 @@ import java.util.Map;
*/
public interface CustomerPropertyMapper extends BaseMapper<String, CustomerProperty> {
int findNextSort(@Param("type") String type);
/**
* find next sort
* @param type type
* @return sort
*/
int findNextSort(
@NotNull
@Param("type")
CustomerProperty.Type type);
/**
* list for customer filter
* @param type
* @return
* @param type type
* @return custom props
*/
List<Map<String, Object>> listForFilter(
@NotBlank
@NotNull
@Param("type")
String type);
CustomerProperty.Type type);
List<CustomerProperty> listApplication(
@NotBlank
@Param("customerId")
String customerId);
}

View File

@ -24,4 +24,14 @@
where type = #{type}
order by sort
</select>
<select id="listApplication" resultType="com.pudonghot.ambition.crm.model.CustomerProperty">
select p.*
from <include refid="table" /> p
join crm_customer_application a
on p.id = a.property_id
where p.type = 'APPLICATION'
and a.id = #{customerId}
order by p.sort
</select>
</mapper>

View File

@ -31,7 +31,7 @@
p:dataSource-ref="dataSource"
p:typeAliasesPackage="com.pudonghot.ambition.crm.model"
p:configLocation="classpath:mybatis/mybatis-config.xml"
p:mapperLocations="classpath*:mybatis/mappers/**/*-mapper.xml" />
p:mapperLocations="classpath*:com/pudonghot/ambition/crm/mapper/*Mapper.xml" />
<!-- MyBatis Mappers Auto Scan -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"
p:basePackage="com.pudonghot.ambition.crm.mapper"

View File

@ -1,6 +1,7 @@
package com.pudonghot.ambition.crm.mapper;
import com.pudonghot.ambition.crm.model.CustomerProperty;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -10,6 +11,8 @@ import org.springframework.test.context.junit4.AbstractTransactionalJUnit4Spring
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* @version 0.0.1
@ -17,6 +20,7 @@ import java.util.Date;
* Tech Support <a href="mailto:chyxion@163.com">Shaun Chyxion</a><br>
* Jun 19, 2017 10:32:35 PM
*/
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath*:spring/spring-*.xml")
public class CustomerPropertyMapperTest extends AbstractTransactionalJUnit4SpringContextTests {
@ -25,44 +29,8 @@ public class CustomerPropertyMapperTest extends AbstractTransactionalJUnit4Sprin
@Test
public void mapperTest() {
// String id = String.valueOf(new Date().getTime());
// init model
CustomerProperty m = new CustomerProperty();
String id = "id";
m.setId(id);
m.setDateCreated(new Date());
m.setType("s");
m.setName("s");
m.setSort(1);
mapper.insert(m);
Assert.assertTrue(mapper.list(null).size() > 0);
/*
// Your Test Logics
CustomerProperty m1 = mapper.find(id);
// asserts
Assert.assertEquals(id, m1.getId());
Assert.assertEquals("s", m.getType());
Assert.assertEquals("s", m.getName());
Assert.assertEquals(1, m.getSort());
// update
m.setDateUpdated(new Date());
m.setType("S");
m.setName("S");
m.setSort(2);
mapper.update(m);
m1 = mapper.find(id);
// asserts
Assert.assertEquals(id, m1.getId());
Assert.assertNotNull(m1.getDateUpdated());
Assert.assertEquals("S", m.getType());
Assert.assertEquals("S", m.getName());
Assert.assertEquals(2, m.getSort());
// list
Assert.assertTrue(mapper.list(null).size() > 0);
// delete
mapper.delete(id);
m1 = mapper.find(id);
Assert.assertNull(m1);
*/
List<Map<String, Object>> maps =
mapper.listForFilter(CustomerProperty.Type.STATUS);
log.info("status: [{}].", maps);
}
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="log.level">DEBUG</Property>
<Property name="log.dir">.logs</Property>
<Property name="pattern">%-d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%t][%c{1}] %m%n</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${pattern}" />
</Console>
<RollingFile name="File"
fileName="${log.dir}/${project.artifactId}.log"
filePattern="${log.dir}/$${date:yyyy-MM}/${project.artifactId}-%d{yyyy-MM-dd}-%i.log">
<PatternLayout pattern="${pattern}" />
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="16 MB" />
</Policies>
<DefaultRolloverStrategy max="32" />
</RollingFile>
</Appenders>
<Loggers>
<Logger name="org.springframework" level="INFO" additivity="false">
<AppenderRef ref="File" />
</Logger>
<Logger name="org.apache" level="WARN" additivity="false">
<AppenderRef ref="File" />
</Logger>
<Logger name="org.hibernate.validator" level="WARN" additivity="false">
<AppenderRef ref="File" />
</Logger>
<Root level="${log.level}" additivity="false">
<AppenderRef ref="File" level="${log.level}" />
<AppenderRef ref="Console" level="${log.level}" />
</Root>
</Loggers>
</Configuration>

View File

@ -1,4 +1,6 @@
# MySQL
db.url=jdbc:mysql://127.0.0.1:43306/pudong_hot?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
db.user=root
db.password=696@2^~)oZ@^#*Q
datasource.host=127.0.0.1
datasource.port=63306
datasource.database-name=ambition_crm
datasource.username=root
datasource.password=696@2^~)oZ@^#*Q

View File

@ -3,7 +3,9 @@ package com.pudonghot.ambition.crm.form.create;
import lombok.Getter;
import lombok.Setter;
import me.chyxion.tigon.form.FC2;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotBlank;
import com.pudonghot.ambition.crm.model.CustomerProperty;
/**
* @version 0.0.1
@ -18,8 +20,8 @@ public class CustomerPropertyFormForCreate extends FC2<String> {
private static final long serialVersionUID = 1L;
// Properties
@NotBlank
private String type;
@NotNull
private CustomerProperty.Type type;
@NotBlank
private String name;
}

View File

@ -22,7 +22,11 @@ public class CustomerProperty extends M3<String, String> {
/**
* customer status: 已获得活动的市场失败项目取消关闭
*/
public static final String TYPE_STATUS = "STATUS";
// public static final String TYPE_STATUS = "STATUS";
public enum Type {
STATUS,
APPLICATION,
}
// system status
public static final String STATUS_ACTIVE_ID = "STATUS_ACTIVE";
@ -41,7 +45,7 @@ public class CustomerProperty extends M3<String, String> {
// Properties
@NotUpdate
private String type;
private Type type;
private String name;
@NotUpdateWhenNull
private Integer sort;

View File

@ -25,7 +25,7 @@
<mongodb.version>3.2.0</mongodb.version>
<tigon.version>0.0.1-SNAPSHOT</tigon.version>
<spring.version>4.3.8.RELEASE</spring.version>
<spring-boot.version>1.5.4.RELEASE</spring-boot.version>
<spring-boot.version>1.5.10.RELEASE</spring-boot.version>
<shiro.version>1.3.2</shiro.version>
<aspectj.version>1.8.10</aspectj.version>
</properties>

View File

@ -1,46 +1,54 @@
import Ember from 'ember';
import BaseComponent from './base-component';
import Component from '@ember/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
export default BaseComponent.extend({
classNames: ['wizard-actions'],
type: 'create',
form: true,
didReceiveAttrs() {
state: service('state'),
successMsg: computed.alias('success-message'),
postUrl: computed.alias('post-url'),
backRouteName: computed.alias('back-route'),
backRouteParams: computed.alias('back-route-params'),
backToList() {
const me = this;
me._super(...arguments);
let backRouteName = me.get('backRouteName');
if (Ember.isNone(backRouteName)) {
let listRouteName = me.get('routeName').replace(/[^.]+$/, 'list');
// find route
if (!me.getRoute(listRouteName)) {
// no list route found, find parent
listRouteName = listRouteName.replace(/[^.]+\.[^.]+$/, 'list');
}
me.set('backRouteName', listRouteName);
// list route params
Ember.isNone(me.get('backRouteParams'))
&& me.set('backRouteParams', 1);
let listRouteName = me.get('routeName').replace(/[^.]+$/, 'list');
// find route
if (!me.getRoute(listRouteName)) {
// no list route found, find parent
listRouteName = listRouteName.replace(/[^.]+\.[^.]+$/, 'list');
}
me.transitionToRoute(listRouteName, 1);
},
transitionToRoute(name, params, queryParams) {
const me = this;
let args = [name];
if (!Ember.isNone(params)) {
if (Ember.isArray(params)) {
args = args.concat(params);
}
else if (params !== false) {
args.push(params);
}
}
Ember.isNone(queryParams) || args.push({queryParams: queryParams});
me.get('router').transitionTo(...args);
},
actions: {
goback() {
let me = this;
let router = me.get('router');
let routeName = me.get('backRouteName');
let routeParams = me.get('backRouteParams');
let queryParams = me.get('backRouteQueryParams');
let params = [routeName];
if (!Ember.isNone(routeParams)) {
if (Ember.isArray(routeParams)) {
params = params.concat(routeParams);
}
else if (routeParams !== false) {
params.push(routeParams);
}
if (routeName) {
me.transitionToRoute(routeName,
me.get('backRouteParams'),
me.get('backRouteQueryParams'));
}
else {
me.get('state').back() || me.backToList();
}
Ember.isNone(queryParams) ||
params.push({queryParams: queryParams});
router.transitionTo(...params);
},
save() {
let me = this;
@ -88,4 +96,4 @@ export default BaseComponent.extend({
getModel() {
return this.get('route.controller.model');
}
});
});

View File

@ -1,26 +1,106 @@
import Ember from 'ember';
import BaseFormInput from './base-form-input';
import { set, observer } from '@ember/object'
import { isNone } from '@ember/utils'
import { uniqBy } from '@ember/object/computed';
import { isArray } from '@ember/array';
export default BaseFormInput.extend({
classNames: ['form-group'],
classNameBindings: ['hasError:has-error'],
didInsertElement() {
let me = this;
uniqueSelected: uniqBy('options', 'selected'),
'value-field': 'value',
'text-field': 'text',
onSelectionChanged: observer('options.@each.selected', function() {
const me = this;
const options = me.get('options');
me.setVal(me.getSelected());
const onChange = me.get('on-change');
onChange && onChange(me.getVal(), options);
}),
didReceiveAttrs() {
const me = this;
me._super(...arguments);
isNone(me.get('options')) && me.set('options', []);
},
didInsertElement() {
const me = this;
me._super(...arguments);
me.prependEmpty();
me.initSelect();
me.$('select.select2').select2({
placeholder: me.get('placeholder') || me.get('label')
allowClear: me.get('nullable'),
placeholder: me.get('placeholder') || me.get('label') || 'Please select...'
}).on('change', function(e) {
if (e.added) {
Ember.set(me.findOption(e.added.id), 'selected', 'selected');
if (e.removed) {
me.unselect(me.findOption(e.removed.id));
}
else if (e.removed) {
Ember.set(me.findOption(e.removed.id), 'selected', false);
if (e.added) {
me.select(me.findOption(e.added.id));
}
});
},
findOption(value) {
return this.get('options').find(option => option.value == value);
didUpdate() {
const me = this;
me._super(...arguments);
me.prependEmpty();
me.$('select.select2').select2('val', me.getSelected());
},
prependEmpty() {
const me = this;
// prepend empty object for nullable
if (me.get('nullable')) {
const firstObject = me.get('options.firstObject');
if (firstObject
&& firstObject[me.get('value-field')]
&& firstObject[me.get('text-field')]) {
me.get('options').unshiftObject({});
}
}
},
initSelect() {
const me = this;
// init select
if (me.get('multiple')) {
const vals = me.getVal();
if (isArray(vals)) {
vals.each(v => me.select(me.findOption(v)));
}
else {
me.setVal(me.getSelected());
}
}
else {
const val = me.getVal();
if (!isNone(val)) {
me.select(me.findOption(val));
}
else {
const selected = me.get('options').find(o => o.selected);
if (selected) {
me.setVal(selected[me.get('value-field')]);
}
else {
me.select(me.get('options.firstObject'))
}
}
}
},
findOption(val) {
const me = this;
// val may empty of nullable option
return val ? me.get('options').find(op => op[me.get('value-field')] == val)
: me.get('options.firstObject');
},
select(op) {
op && set(op, 'selected', true);
},
unselect(op) {
op && set(op, 'selected', false);
},
getSelected() {
const me = this;
const selected = me.get('options').filter(o => o.selected).mapBy(me.get('value-field'));
return selected.length > 1 ? selected : selected.length == 1 ? selected[0] : null;
}
});

View File

@ -0,0 +1,34 @@
import Component from '@ember/component';
import { filter } from '@ember/object/computed';
const OptionTextComponent = Component.extend({
tagName: '',
'value-field': 'value',
'text-field': 'text',
didReceiveAttrs() {
const me = this;
me._super(...arguments);
let val = me.get('value');
if (val) {
const option = (me.get('options') || []).findBy(me.get('value-field'), val);
if (option) {
let text = option[me.get('text-field')];
let textExp = me.get('text-exp');
me.set('text', textExp ?
textExp.replace('$value', option[valueField])
.replace('$text', text)
.replace(/\$\.\w+/g, field => option[field.replace('$.', '')] || '') :
text);
}
else {
me.set('text', 'N/A');
}
}
}
});
OptionTextComponent.reopenClass({
positionalParams: ['options', 'value', 'value-field', 'text-field', 'text-exp'],
});
export default OptionTextComponent;

View File

@ -15,14 +15,43 @@ export default BaseComponent.extend({
currPage: Ember.computed('params.page', function() {
let me = this;
let currPage = parseInt(me.get('params.page'));
Ember.Logger.info('Get Curr Page [' + currPage + ']');
Ember.Logger.info('Get curr page [' + currPage + ']');
return currPage;
}),
pages: Ember.computed('total', function() {
let i = 0;
const me = this;
const totalPage = me.get('totalPage');
const currPage = me.get('currPage');
let pages = [];
while (i++ < this.get('totalPage')) {
pages.push(i);
if (currPage > 4) {
pages.push({page: 1, text: 1});
pages.push({page: currPage - 3, text: '...'});
pages.push({page: currPage - 2, text: currPage - 2});
pages.push({page: currPage - 1, text: currPage - 1});
}
// from page 1
else {
let i = 0;
while (++i < currPage) {
pages.push({page: i, text: i});
}
}
pages.push({page: currPage, text: currPage, active: true});
if (currPage < totalPage - 4) {
pages.push({page: currPage + 1, text: currPage + 1});
pages.push({page: currPage + 2, text: currPage + 2});
pages.push({page: currPage + 3, text: '...'});
pages.push({page: totalPage, text: totalPage});
}
// to the end
else {
let i = currPage;
while (++i <= totalPage) {
pages.push({page: i, text: i});
}
}
return pages;
}),

View File

@ -0,0 +1,15 @@
import { helper } from '@ember/component/helper';
export function optionTextExp([option, valueField, textField, textExp]) {
const text = option[textField];
return textExp ? textExp.replace('$value', option[valueField])
.replace('$text', text)
.replace(/\$\.\w+/g, (field) => option[field.replace('$.', '')] || '') : text;
// '1234567890'.replace(/\d/g, function(c) {
// return ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'][parseInt(c)];
// });
}
export default helper(optionTextExp);

View File

@ -0,0 +1,18 @@
import Helper from '@ember/component/helper';
import { get, computed } from '@ember/object';
import { getOwner } from '@ember/application';
import { assert } from '@ember/debug';
export default Helper.extend({
router: computed(function() {
return getOwner(this).lookup('router:main');
}).readOnly(),
route: computed(function() {
return getOwner(this).lookup('route:' + this.get('router.currentRouteName'));
}).readOnly(),
compute([attr]) {
let route = get(this, 'route');
assert('[ember-route-action-helper] Unable to lookup router', route);
return get(route, attr);
}
});

View File

@ -5,6 +5,7 @@ export function initialize(app) {
app.inject('route', 'message', 'service:message');
app.inject('route', 'dialog', 'service:dialog');
app.inject('route', 'router', 'router:main');
app.inject('route', 'state', 'service:state');
// controller
app.inject('controller', 'ajax', 'service:ajax');

View File

@ -0,0 +1,15 @@
import Route from '@ember/routing/route';
export function initialize(/* appInstance */) {
Route.reopen({
afterModel() {
const me = this;
me._super(...arguments);
me.get('no-state') || me.get('state').push(me.get('router.currentURL'));
}
});
}
export default {
initialize
};

View File

@ -58,6 +58,12 @@ Router.map(function() {
this.route('import-record', function() {
this.route('list', {path: '/list/:page'});
});
this.route('customer-application', function() {
this.route('list', {path: '/list/:page'});
this.route('create');
this.route('edit', {path: '/:id/edit'});
});
});
export default Router;

View File

@ -0,0 +1,14 @@
import Ember from 'ember';
import BaseRoute from '../base';
export default BaseRoute.extend({
service: Ember.inject.service('customer-property.service'),
breadcrumbs: [{route: 'customer-application.list', params: 1, text: 'Customer Application'},
{text: 'Create Customer Application'}],
model() {
return {
type: 'APPLICATION',
enabled: true
};
}
});

View File

@ -0,0 +1,11 @@
import Ember from 'ember';
import BaseEditRoute from '../base-edit';
export default BaseEditRoute.extend({
service: Ember.inject.service('customer-property.service'),
afterModel(model) {
this.set('breadcrumbs',
[{route: 'customer-application.list', params: 1, text: 'Customer Application'},
{text: 'Edit Customer Application[' + model.name + ']'}]);
}
});

View File

@ -0,0 +1,9 @@
import Ember from 'ember';
import CustomerPropertyListRoute from './../customer-property/list';
export default CustomerPropertyListRoute.extend({
breadcrumbs: [{text: 'Customer Application'}],
extraParams() {
return {type: 'APPLICATION'};
}
});

View File

@ -0,0 +1,34 @@
import Ember from 'ember';
import BaseListRoute from './../base-list';
export default BaseListRoute.extend({
service: Ember.inject.service('customer-property.service'),
actions: {
moveUp(it) {
let me = this;
let data = me.get('controller.model.data');
if (data && data.length > 1) {
let index = data.indexOf(it);
data.removeObject(it);
data.insertAt(index - 1, it);
me.updateSort();
}
},
moveDown(it) {
let me = this;
let data = me.get('controller.model.data');
if (data && data.length > 1) {
let index = data.indexOf(it);
data.removeObject(it);
data.insertAt(index + 1, it);
me.updateSort();
}
}
},
updateSort() {
let me = this;
me.get('ajax').doPost('customer-property/update-sort', {
ids: JSON.stringify(me.get('controller.model.data').mapBy('id'))
}, false);
}
});

View File

@ -2,11 +2,11 @@ import Ember from 'ember';
import BaseRoute from '../base';
export default BaseRoute.extend({
service: Ember.inject.service('customer-property.service'),
breadcrumbs: [{route: 'customer-status.list', params: 1, text: 'Customer Status'},
{text: 'Create Customer Status'}],
model() {
return {
gender: 'M',
type: 'STATUS',
enabled: true
};

View File

@ -2,6 +2,7 @@ import Ember from 'ember';
import BaseEditRoute from '../base-edit';
export default BaseEditRoute.extend({
service: Ember.inject.service('customer-property.service'),
afterModel(model) {
this.set('breadcrumbs',
[{route: 'customer-status.list', params: 1, text: 'Customer Status'},

View File

@ -2,12 +2,12 @@ import Ember from 'ember';
import BaseListSelectRoute from '../base-list-select';
export default BaseListSelectRoute.extend({
service: Ember.inject.service('customer-property.service'),
extraParams() {
return {
type: 'STATUS',
enabled: true
};
},
templateName: 'customer-status/list-select',
service: Ember.inject.service('customer-status.service')
templateName: 'customer-status/list-select'
});

View File

@ -1,37 +1,9 @@
import Ember from 'ember';
import BaseListRoute from './../base-list';
import CustomerPropertyListRoute from './../customer-property/list';
export default BaseListRoute.extend({
export default CustomerPropertyListRoute.extend({
breadcrumbs: [{text: 'Customer Status'}],
extraParams() {
return {type: 'STATUS'};
},
actions: {
moveUp(it) {
let me = this;
let data = me.get('controller.model.data');
if (data && data.length > 1) {
let index = data.indexOf(it);
data.removeObject(it);
data.insertAt(index - 1, it);
me.updateSort();
}
},
moveDown(it) {
let me = this;
let data = me.get('controller.model.data');
if (data && data.length > 1) {
let index = data.indexOf(it);
data.removeObject(it);
data.insertAt(index + 1, it);
me.updateSort();
}
}
},
updateSort() {
let me = this;
me.get('ajax').doPost('customer-property/update-sort', {
ids: JSON.stringify(me.get('controller.model.data').mapBy('id'))
}, false);
}
});

View File

@ -6,5 +6,8 @@ export default BaseEditRoute.extend({
this.set('breadcrumbs',
[{route: 'customer.list', params: 1, text: 'Customers'},
{text: 'Edit Customer [' + model.id + ']'}]);
model.applicationList.forEach(a => {
a.selected = model.applications.includes(a.id);
});
}
});

View File

@ -16,6 +16,7 @@ const cols = ['showId',
'showIssue1',
'showIssue2',
'showIssue3',
'showApplication',
];
const config = {
showAll: false,
@ -44,7 +45,15 @@ const config = {
};
cols.forEach((col) => {
config[col] = !['showCountryCode', 'showState', 'showCity'].includes(col);
config[col] = ![
'showIssue2',
'showIssue3',
'showDateAdded',
'showYears',
'showCountryCode',
'showRegion',
'showState',
].includes(col);
});
config.countOfShowing = cols.filter(col => {
return config[col];

29
web/app/services/state.js Normal file
View File

@ -0,0 +1,29 @@
import Service from '@ember/service';
import { getOwner } from '@ember/application';
export default Service.extend({
states: [],
max: 16,
previous() {
return this.get('states.lastObject');
},
push(url) {
if (url) {
const me = this;
me.get('states').pushObject(url);
while (me.get('states.length') > me.get('max')) {
me.get('states').shiftObject();
}
}
},
back() {
const me = this;
const url = me.previous();
// if (pre && pre.retry();
if (url) {
const router = getOwner(this).lookup('router:main');
router.transitionTo(url);
}
return url;
}
});

View File

@ -1,16 +1,25 @@
<label for="{{name}}" class="{{get this 'label-class'}} control-label no-padding-right"> {{label}} </label>
<label class="{{get this 'label-class'}} control-label no-padding-right">{{label}}</label>
<div class="{{get this 'input-class'}} no-padding-right">
<div class="row col-xs-12 no-padding">
<div class="col-xs-{{get this 'col-width'}} no-padding-right">
<select name=name
multiple={{multiple}}
<select multiple={{multiple}}
data-placeholder={{if placeholder placeholder label}}
class="select2"
style="width: 100%;">
{{#each options as |option|}}
<option value={{option.value}} selected={{option.selected}}>{{option.text}}</option>
<option value={{get option (get this 'value-field')}} selected={{if option.selected 'selected'}}>
{{!text-exp: $text($value)}}
{{#if (get option (get this 'value-field'))}}
{{option-text-exp option (get this 'value-field') (get this 'text-field') (get this 'text-exp')}}
{{/if}}
</option>
{{/each}}
</select>
{{#each options as |option|}}
{{#if option.selected}}
<input type="hidden" name="{{name}}" value="{{get option (get this 'value-field')}}" />
{{/if}}
{{/each}}
</div>
</div>
</div>

View File

@ -64,6 +64,12 @@
<span class="menu-text"> Customer Status </span>
{{/link-to}}
</li>
<li>
{{#link-to 'customer-application.list' 1}}
<i class="menu-icon fa fa-cube blue"></i>
<span class="menu-text"> Customer Application </span>
{{/link-to}}
</li>
<li>
{{#link-to 'import-record.list' 1}}
<i class="menu-icon fa fa-th-list blue"></i>

View File

@ -0,0 +1 @@
{{text}}

View File

@ -7,6 +7,7 @@
No Record
{{/if}}
</h6>
<ul class="pagination pull-right no-margin">
<li class="prev">
<a href="#" {{action 'gotoPage' prevPage}}>
@ -15,9 +16,9 @@
</li>
{{#each pages as |page|}}
<li class="{{if (eq page currPage) 'active'}}">
<a href="#" {{action 'gotoPage' page}}>
{{page}}
<li class="{{if page.active 'active'}}">
<a href="#" {{action 'gotoPage' page.page}}>
{{page.text}}
</a>
</li>
{{/each}}

View File

@ -0,0 +1,8 @@
{{#form-content}}
{{form-input type='hidden' name='type'}}
{{form-input name='name' label='Name'}}
{{form-input-enabled label='Enabled' enabledText='TRUE' disabledText='FALSE'}}
{{form-input name='note' label='Remark'}}
<hr />
{{form-footer-buttons}}
{{/form-content}}

View File

@ -0,0 +1,9 @@
{{#form-content}}
{{input type='hidden' name='id' value=model.id}}
{{form-input type='hidden' name='type'}}
{{form-input name='name' label='Name'}}
{{form-input-enabled label='Enabled' enabledText='TRUE' disabledText='FALSE'}}
{{form-input name='note' label='Remark'}}
<hr />
{{form-footer-buttons type='update'}}
{{/form-content}}

View File

@ -0,0 +1,107 @@
{{#main-content}}
<div class="widget-box transparent">
{{#grid-header}}
<li>
{{#link-to 'customer-application.create'}}
<i class="ace-icon fa fa-plus-circle bigger-110 green"></i>
Create Customer Application
{{/link-to}}
</li>
{{/grid-header}}
<div class="widget-body">
<!-- #section:custom/scrollbar -->
<div class="widget-main no-padding table-responsive no-border">
<table class="table table-striped table-bordered table-hover dataTable" style="border: 1px solid #ddd;">
<thead class="thin-border-bottom">
<tr>
<th>
Name
</th>
<th>
<i class="ace-icon fa fa-sticky-note-o bigger-110 hidden-480"></i>
Remark
</th>
<th>
<i class="ace-icon fa fa-exchange bigger-110 hidden-480"></i>
Enabled
</th>
<th>
<i class="ace-icon fa fa-cogs bigger-110 hidden-480"></i>
Settings
</th>
</tr>
</thead>
<tbody>
{{#each model.data as |it|}}
<tr>
<td>
{{it.name}}
</td>
<td>
{{editable-cell model=it field='note'}}
</td>
<td>
{{status-cell model=it enabledText='TRUE' disabledText='FALSE'}}
</td>
<td>
<div class="hidden-sm hidden-xs btn-group">
{{status-toggle-button model=it}}
{{#link-to 'customer-application.edit' it.id class='btn btn-xs btn-info' title='Edit'}}
<i class="ace-icon fa fa-pencil bigger-120"></i>
{{/link-to}}
{{#if (not-eq model.data.firstObject.id it.id)}}
<button class="btn btn-xs btn-purple" title="Move Up" {{action (route-action 'moveUp' it)}}>
<i class="ace-icon fa fa-arrow-up bigger-120"></i>
</button>
{{/if}}
{{#if (not-eq model.data.lastObject.id it.id)}}
<button class="btn btn-xs" title="Move Down" {{action (route-action 'moveDown' it)}}>
<i class="ace-icon fa fa-arrow-down bigger-120"></i>
</button>
{{/if}}
</div>
<div class="hidden-md hidden-lg">
<div class="inline pos-rel">
<button class="btn btn-minier btn-primary dropdown-toggle" data-toggle="dropdown" data-position="auto">
<i class="ace-icon fa fa-cog icon-only bigger-110"></i>
</button>
<ul class="dropdown-menu dropdown-only-icon dropdown-yellow dropdown-menu-right dropdown-caret dropdown-close">
<li>
{{status-toggle-button model=it iconOnly=true}}
</li>
<li>
{{#link-to 'customer-status.edit' it.id class='tooltip-info' data-rel='tooltip' title='Edit'}}
<span class="blue">
<i class="ace-icon fa fa-pencil-square-o bigger-120"></i>
</span>
{{/link-to}}
</li>
<li>
{{#if (not-eq model.data.firstObject.id it.id)}}
<a class="tooltip-info" title="Move Up" {{action (route-action 'moveUp' it)}}>
<i class="ace-icon fa fa-arrow-up bigger-120"></i>
</a>
{{/if}}
{{#if (not-eq model.data.lastObject.id it.id)}}
<a class="tooltip-info" title="Move Down" {{action (route-action 'moveDown' it)}}>
<i class="ace-icon fa fa-arrow-down bigger-120"></i>
</a>
{{/if}}
</li>
</ul>
</div>
</div>
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
{{pagination-bar}}
</div>
</div>
{{/main-content}}

View File

@ -3,6 +3,7 @@
{{#grid-header}}
<li>
{{#link-to 'customer-status.create'}}
<i class="ace-icon fa fa-plus-circle bigger-110 green"></i>
Create Customer Status
{{/link-to}}
</li>

View File

@ -9,12 +9,23 @@
{{form-input name='salesperson' label='Salesperson' readonly='readonly'}}
{{form-input type='hidden' name='enabled'}}
{{form-input-modal-select
listRoute='customer.edit.customer-status-select'
idField='status'
nameField='statusText'
btnIcon='fa-file-image-o'
label='Status'}}
{{form-input-select2
multiple=true
name='applications'
label='Application'
nullable=true
options=model.applicationList
value-field='id'
text-field='name'
}}
{{form-input-select2
name='status'
label='Status'
options=model.statusList
value-field='id'
text-field='name'
}}
<hr />
{{form-footer-buttons type='update'}}
{{/form-content}}

View File

@ -39,12 +39,48 @@
<table class="table table-striped table-bordered table-hover" style="border: 1px solid #ddd;">
<thead class="thin-border-bottom">
<tr>
{{!--
1 ID
2 Name
3 Sales(3Yrs)
4 Comment
5 Application
6 Status
7 Added
8 Year
9 Country
10 State
11 City
12 MS
13 Region
14 Slspsn
--}}
{{#if tableOptions.showId}}
{{sortable-th name='id' text='ID'}}
{{/if}}
{{#if tableOptions.showName}}
<th style='min-width: 86px; max-width: 168px;'>Name</th>
{{/if}}
{{#if tableOptions.showYtdSale}}
{{sortable-th name='sumYtdSales' text='Sales(3Yrs)' style='min-width: 126px;'}}
{{/if}}
{{#if tableOptions.showIssue1}}
<th>Comment</th>
{{/if}}
{{#if tableOptions.showIssue2}}
<th>Comment 2</th>
{{/if}}
{{#if tableOptions.showIssue3}}
<th>Comment 3</th>
{{/if}}
{{#if tableOptions.showApplication}}
<th>Application</th>
{{/if}}
{{#if tableOptions.showStatus}}
{{#sortable-th name='status' style='min-width: 96px;'}}
{{th-filter name='status' text='Status' label='Status Filter' options=model.statusList}}
{{/sortable-th}}
{{/if}}
{{#if tableOptions.showDateAdded}}
{{#sortable-th name='dateAdded' style='min-width: 96px;'}}
{{th-filter name='dateAdded' text='Added' label='Added Filter' options=model.dateAddedList}}
@ -55,9 +91,6 @@
{{th-filter name='year' text='Year' label='Year Filter' options=model.latestYearList}}
{{/sortable-th}}
{{/if}}
{{#if tableOptions.showYtdSale}}
{{sortable-th name='sumYtdSales' text='Sales(3Yrs)' style='min-width: 126px;'}}
{{/if}}
{{#if tableOptions.showCountryCode}}
<th>
{{th-filter name='countryCode' text='Country' label='Country Filter' options=model.countryCodeList}}
@ -88,20 +121,6 @@
{{th-filter name='salesperson' text='Slspsn' label='Salesperson Filter' options=model.salespersonList}}
{{/sortable-th}}
{{/if}}
{{#if tableOptions.showStatus}}
{{#sortable-th name='status' style='min-width: 96px;'}}
{{th-filter name='status' text='Status' label='Status Filter' options=model.statusList}}
{{/sortable-th}}
{{/if}}
{{#if tableOptions.showIssue1}}
<th>Comment 1</th>
{{/if}}
{{#if tableOptions.showIssue2}}
<th>Comment 2</th>
{{/if}}
{{#if tableOptions.showIssue3}}
<th>Comment 3</th>
{{/if}}
<th>
<i class="ace-icon fa fa-cogs bigger-110 hidden-480"></i>
Settings
@ -124,23 +143,6 @@
{{it.name}}
</td>
{{/if}}
{{#if tableOptions.showDateAdded}}
<td>
{{date-cell value=it.dateAdded format='YYYY'}}
</td>
{{/if}}
{{#if tableOptions.showYears}}
<td>
{{!--
{{#if (eq it.years.length 1)}}
{{it.years.firstObject}}
{{else if (gt it.years.length 1)}}
{{it.years.firstObject}}-{{it.years.lastObject}}
{{/if}}
--}}
{{it.year}}
</td>
{{/if}}
{{#if tableOptions.showYtdSale}}
<td>
{{#if it.countYtdSales}}
@ -157,41 +159,6 @@
{{number-us it.sumYtdSales}}
</td>
{{/if}}
{{#if tableOptions.showCountryCode}}
<td>
{{it.countryCode}}
</td>
{{/if}}
{{#if tableOptions.showState}}
<td>
{{it.state}}
</td>
{{/if}}
{{#if tableOptions.showCity}}
<td>
{{it.city}}
</td>
{{/if}}
{{#if tableOptions.showMs}}
<td>
{{it.ms}}
</td>
{{/if}}
{{#if tableOptions.showRegion}}
<td>
{{it.region}}
</td>
{{/if}}
{{#if tableOptions.showSalesperson}}
<td>
{{it.salesperson}}
</td>
{{/if}}
{{#if tableOptions.showStatus}}
<td>
{{it.statusText}}
</td>
{{/if}}
{{#if tableOptions.showIssue1}}
<td style="max-width: 36px; white-space: initial;">
{{#if it.issue1}}
@ -240,6 +207,63 @@
{{/if}}
</td>
{{/if}}
{{#if tableOptions.showApplication}}
<td>
{{it.application}}
</td>
{{/if}}
{{#if tableOptions.showStatus}}
<td>
{{it.statusText}}
</td>
{{/if}}
{{#if tableOptions.showDateAdded}}
<td>
{{date-cell value=it.dateAdded format='YYYY'}}
</td>
{{/if}}
{{#if tableOptions.showYears}}
<td>
{{!--
{{#if (eq it.years.length 1)}}
{{it.years.firstObject}}
{{else if (gt it.years.length 1)}}
{{it.years.firstObject}}-{{it.years.lastObject}}
{{/if}}
--}}
{{it.year}}
</td>
{{/if}}
{{#if tableOptions.showCountryCode}}
<td>
{{it.countryCode}}
</td>
{{/if}}
{{#if tableOptions.showState}}
<td>
{{it.state}}
</td>
{{/if}}
{{#if tableOptions.showCity}}
<td>
{{it.city}}
</td>
{{/if}}
{{#if tableOptions.showMs}}
<td>
{{it.ms}}
</td>
{{/if}}
{{#if tableOptions.showRegion}}
<td>
{{it.region}}
</td>
{{/if}}
{{#if tableOptions.showSalesperson}}
<td>
{{it.salesperson}}
</td>
{{/if}}
<td>
<div class="hidden-sm hidden-xs btn-group">
{{#link-to 'customer-issue.create' it.id class='btn btn-xs btn-success' data-rel='tooltip' title='Write Comment'}}

View File

@ -12,21 +12,23 @@
<div class="col-xs-5 col-sm-5">
<div class="control-group">
{{ace-checkbox label='ID' value=model.tableOptions.showId}}
{{ace-checkbox label='Name' value=model.tableOptions.showName}}
{{ace-checkbox label='Year' value=model.tableOptions.showYears}}
{{ace-checkbox label='Date Added' value=model.tableOptions.showDateAdded}}
{{ace-checkbox label='YTD Sales' value=model.tableOptions.showYtdSale}}
{{ace-checkbox label='Application' value=model.tableOptions.showApplication}}
{{ace-checkbox label='Date Added' value=model.tableOptions.showDateAdded}}
{{ace-checkbox label='Country' value=model.tableOptions.showCountryCode}}
{{ace-checkbox label='State' value=model.tableOptions.showState}}
{{ace-checkbox label='City' value=model.tableOptions.showCity}}
{{ace-checkbox label='Region' value=model.tableOptions.showRegion}}
</div>
</div>
<div class="col-xs-5 col-sm-5">
<div class="control-group">
{{ace-checkbox label='City' value=model.tableOptions.showCity}}
{{ace-checkbox label='MS' value=model.tableOptions.showMs}}
{{ace-checkbox label='Region' value=model.tableOptions.showRegion}}
{{ace-checkbox label='Salesperson' value=model.tableOptions.showSalesperson}}
{{ace-checkbox label='Name' value=model.tableOptions.showName}}
{{ace-checkbox label='Comment' value=model.tableOptions.showIssue1}}
{{ace-checkbox label='Status' value=model.tableOptions.showStatus}}
{{ace-checkbox label='Year' value=model.tableOptions.showYears}}
{{ace-checkbox label='State' value=model.tableOptions.showState}}
{{ace-checkbox label='MS' value=model.tableOptions.showMs}}
{{ace-checkbox label='Salesperson' value=model.tableOptions.showSalesperson}}
{{ace-checkbox label='Select All' value=model.tableOptions.showAll}}
</div>
</div>

View File

@ -3,6 +3,7 @@
{{#grid-header}}
<li>
{{#link-to 'user.create'}}
<i class="ace-icon fa fa-plus-circle bigger-110 green"></i>
Create User
{{/link-to}}
</li>

1102
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,16 +23,18 @@
"ember-cli": "~2.14.2",
"ember-cli-app-version": "^3.0.0",
"ember-cli-babel": "^6.3.0",
"ember-cli-shims": "^1.1.0",
"ember-cli-dependency-checker": "^1.3.0",
"ember-cli-eslint": "^3.0.0",
"ember-cli-htmlbars": "^2.0.1",
"ember-cli-htmlbars-inline-precompile": "^0.4.3",
"ember-cli-inject-live-reload": "^1.4.1",
"ember-cli-jshint": "^1.0.0",
"ember-cli-less": "^1.5.5",
"ember-cli-moment-shim": "1.2.0",
"ember-cli-qunit": "^4.0.0",
"ember-cli-release": "0.2.8",
"ember-cli-shims": "^1.1.0",
"ember-cli-sri": "^2.1.0",
"ember-cli-uglify": "^1.2.0",
"ember-export-application-global": "^2.0.0",
"ember-load-initializers": "^1.0.0",
@ -40,14 +42,12 @@
"ember-radio-button": "1.1.1",
"ember-resolver": "^4.0.0",
"ember-route-action-helper": "2.0.3",
"ember-source": "~2.14.1",
"ember-source": "~2.16.0",
"ember-truth-helpers": "1.1.0",
"emberx-select": "3.1.0",
"emberx-select": "3.0.1",
"glob": "^4.5.3",
"loader.js": "^4.2.3",
"morgan": "^1.7.0",
"ember-cli-sri": "^2.1.0",
"valid-url": "^1.0.9"
"morgan": "^1.7.0"
},
"dependencies": {},
"engines": {

View File

@ -0,0 +1,26 @@
import Application from '@ember/application';
import { run } from '@ember/runloop';
import { initialize } from 'ambition-crm/instance-initializers/state';
import { module, test } from 'qunit';
import destroyApp from '../../helpers/destroy-app';
module('Unit | Instance Initializer | state', {
beforeEach() {
run(() => {
this.application = Application.create();
this.appInstance = this.application.buildInstance();
});
},
afterEach() {
run(this.appInstance, 'destroy');
destroyApp(this.application);
}
});
// Replace this with your real tests.
test('it works', function(assert) {
initialize(this.appInstance);
// you would normally confirm the results of the initializer here
assert.ok(true);
});

View File

@ -0,0 +1,11 @@
import { moduleFor, test } from 'ember-qunit';
moduleFor('route:customer-application/create', 'Unit | Route | customer application/create', {
// Specify the other units that are required for this test.
// needs: ['controller:foo']
});
test('it exists', function(assert) {
let route = this.subject();
assert.ok(route);
});

View File

@ -0,0 +1,11 @@
import { moduleFor, test } from 'ember-qunit';
moduleFor('route:customer-application/edit', 'Unit | Route | customer application/edit', {
// Specify the other units that are required for this test.
// needs: ['controller:foo']
});
test('it exists', function(assert) {
let route = this.subject();
assert.ok(route);
});

View File

@ -0,0 +1,11 @@
import { moduleFor, test } from 'ember-qunit';
moduleFor('route:customer-application/list', 'Unit | Route | customer application/list', {
// Specify the other units that are required for this test.
// needs: ['controller:foo']
});
test('it exists', function(assert) {
let route = this.subject();
assert.ok(route);
});

View File

@ -0,0 +1,12 @@
import { moduleFor, test } from 'ember-qunit';
moduleFor('service:state', 'Unit | Service | state', {
// Specify the other units that are required for this test.
// needs: ['service:foo']
});
// Replace this with your real tests.
test('it exists', function(assert) {
let service = this.subject();
assert.ok(service);
});