Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d65424e5bf | ||
|
|
f0b72b70e7 | ||
|
|
daa5a6aa5e | ||
|
|
2a35793baf | ||
|
|
4a157e9578 | ||
|
|
eabb4c6aa6 | ||
|
|
d22df698b6 | ||
|
|
cb6051cc41 | ||
|
|
1d1009b121 | ||
|
|
94f3fbb867 |
@@ -11,7 +11,7 @@
|
||||
|
||||
<groupId>com.imyeyu.timiserverapi</groupId>
|
||||
<artifactId>TimiServerAPI</artifactId>
|
||||
<version>1.0.40</version>
|
||||
<version>1.0.43</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>TimiServerAPI</name>
|
||||
<description>imyeyu.com API</description>
|
||||
@@ -215,6 +215,11 @@
|
||||
<artifactId>tinypinyin</artifactId>
|
||||
<version>2.0.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.hankcs</groupId>
|
||||
<artifactId>hanlp</artifactId>
|
||||
<version>portable-1.8.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.imyeyu.font.icon</groupId>
|
||||
<artifactId>timi-icon-font</artifactId>
|
||||
|
||||
@@ -5,6 +5,8 @@ import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
|
||||
import com.imyeyu.api.modules.gao.service.GaoCustomerChartService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerChartReq;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseWordReq;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseWordView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
@@ -51,4 +53,15 @@ public class GaoCustomerChartController {
|
||||
req.setStoreId(storeScopeService.resolveStoreId(req.getStoreId()));
|
||||
return service.eventRecordCalendar(req);
|
||||
}
|
||||
|
||||
/// 查询客户病症词频
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.STAT_READ)
|
||||
@PostMapping("/customer/disease/word")
|
||||
public List<GaoCustomerDiseaseWordView> customerDiseaseWord(@RequestBody GaoCustomerDiseaseWordReq req) {
|
||||
TimiException.required(req, "not found req");
|
||||
req.setStoreId(storeScopeService.resolveStoreId(req.getStoreId()));
|
||||
return service.customerDiseaseWord(req);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
|
||||
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
|
||||
import com.imyeyu.api.modules.gao.service.GaoStoreChartService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerAgeStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView;
|
||||
@@ -54,6 +55,15 @@ public class GaoStoreChartController {
|
||||
return service.customerGender(storeScopeService.resolveStoreId(storeId));
|
||||
}
|
||||
|
||||
/// 查询客户年龄分布
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.STAT_READ)
|
||||
@PostMapping("/customer/age")
|
||||
public List<GaoCustomerAgeStatView> customerAge(@RequestParam String storeId) {
|
||||
return service.customerAge(storeScopeService.resolveStoreId(storeId));
|
||||
}
|
||||
|
||||
/// 查询登记事件每日统计,可同时支撑登记趋势和事件结构图表
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.imyeyu.api.modules.gao.mapper;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoCustomer;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerAgeStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateCustomerView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseTextView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerPage;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
@@ -27,6 +30,14 @@ public interface GaoCustomerMapper extends BaseMapper<GaoCustomer, String> {
|
||||
/// @return 每日新增客户数
|
||||
List<GaoCustomerDailyStateView> stateDailyNewCustomer(@Param("storeId") String storeId, @Param("beginCreatedAt") Long beginCreatedAt, @Param("endCreatedAt") Long endCreatedAt);
|
||||
|
||||
/// 查询每日新增客户摘要
|
||||
///
|
||||
/// @param storeId 门店 ID
|
||||
/// @param beginCreatedAt 开始创建时间
|
||||
/// @param endCreatedAt 结束创建时间
|
||||
/// @return 每日新增客户摘要
|
||||
List<GaoCustomerDailyStateCustomerView> selectDailyNewCustomer(@Param("storeId") String storeId, @Param("beginCreatedAt") Long beginCreatedAt, @Param("endCreatedAt") Long endCreatedAt);
|
||||
|
||||
/// 统计开始时间前客户总数
|
||||
///
|
||||
/// @param beginCreatedAt 开始创建时间
|
||||
@@ -38,6 +49,18 @@ public interface GaoCustomerMapper extends BaseMapper<GaoCustomer, String> {
|
||||
/// @return 性别统计列表
|
||||
List<GaoCustomerGenderStatView> stateGender(@Param("storeId") String storeId);
|
||||
|
||||
/// 统计客户年龄分布
|
||||
///
|
||||
/// @param storeId 门店 ID
|
||||
/// @return 客户年龄列表
|
||||
List<GaoCustomerAgeStatView> stateAge(@Param("storeId") String storeId);
|
||||
|
||||
/// 查询客户病症原始文本
|
||||
///
|
||||
/// @param storeId 门店 ID
|
||||
/// @return 客户备注和病症
|
||||
List<GaoCustomerDiseaseTextView> selectDiseaseTextByStoreId(@Param("storeId") String storeId);
|
||||
|
||||
/// 查询客户总数
|
||||
///
|
||||
/// @param page 查询条件
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.imyeyu.api.modules.gao.service;
|
||||
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerChartReq;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseWordReq;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseWordView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView;
|
||||
|
||||
@@ -17,4 +19,7 @@ public interface GaoCustomerChartService {
|
||||
|
||||
/// 查询客户登记日历记录
|
||||
List<GaoEventRecordCalendarView> eventRecordCalendar(GaoCustomerChartReq req);
|
||||
|
||||
/// 查询客户病症词频
|
||||
List<GaoCustomerDiseaseWordView> customerDiseaseWord(GaoCustomerDiseaseWordReq req);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.imyeyu.api.modules.gao.service;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoCustomer;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerAgeStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerPage;
|
||||
@@ -87,4 +88,10 @@ public interface GaoCustomerService extends BaseService<GaoCustomer, String> {
|
||||
/// @param storeId 门店 ID
|
||||
/// @return 性别统计列表
|
||||
List<GaoCustomerGenderStatView> stateGender(String storeId);
|
||||
|
||||
/// 统计客户年龄分布
|
||||
///
|
||||
/// @param storeId 门店 ID
|
||||
/// @return 客户年龄列表
|
||||
List<GaoCustomerAgeStatView> stateAge(String storeId);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,14 @@ public interface GaoProductSkuService extends BaseService<GaoProductSku, String>
|
||||
/// 查询并校验可销售 SKU 单位
|
||||
GaoProductSkuUnitSelection getSellableSkuUnit(String storeId, String skuId, String skuUnitId);
|
||||
|
||||
/// 查询并校验可用于历史订单履约的 SKU 单位
|
||||
///
|
||||
/// @param storeId 门店 ID
|
||||
/// @param skuId SKU ID
|
||||
/// @param skuUnitId SKU 单位 ID
|
||||
/// @return SKU 单位选择结果
|
||||
GaoProductSkuUnitSelection getFulfillableSkuUnit(String storeId, String skuId, String skuUnitId);
|
||||
|
||||
/// 查询并校验 SKU 单位
|
||||
GaoProductSkuUnitSelection getSkuUnit(String storeId, String skuId, String skuUnitId, boolean purchase);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.imyeyu.api.modules.gao.service;
|
||||
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerAgeStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView;
|
||||
@@ -21,6 +22,9 @@ public interface GaoStoreChartService {
|
||||
/// 查询客户性别统计
|
||||
List<GaoCustomerGenderStatView> customerGender(String storeId);
|
||||
|
||||
/// 查询客户年龄分布
|
||||
List<GaoCustomerAgeStatView> customerAge(String storeId);
|
||||
|
||||
/// 查询登记事件每日统计
|
||||
List<GaoEventRecordDailyStatView> eventRecordDailyStat(GaoStoreChartReq req);
|
||||
|
||||
|
||||
+45
@@ -1,15 +1,23 @@
|
||||
package com.imyeyu.api.modules.gao.service.implement;
|
||||
|
||||
import com.hankcs.hanlp.seg.common.Term;
|
||||
import com.hankcs.hanlp.tokenizer.StandardTokenizer;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoCustomerMapper;
|
||||
import com.imyeyu.api.modules.gao.service.GaoCustomerChartService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoEventRecordService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerChartReq;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseTextView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseWordReq;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseWordView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/// 客户报表服务实现
|
||||
///
|
||||
@@ -20,6 +28,7 @@ import java.util.List;
|
||||
public class GaoCustomerChartServiceImplement implements GaoCustomerChartService {
|
||||
|
||||
private final GaoEventRecordService eventRecordService;
|
||||
private final GaoCustomerMapper customerMapper;
|
||||
|
||||
@Override
|
||||
public List<GaoEventRecordDailyStatView> eventRecordDailyStat(GaoCustomerChartReq req) {
|
||||
@@ -33,6 +42,42 @@ public class GaoCustomerChartServiceImplement implements GaoCustomerChartService
|
||||
return eventRecordService.calendarByCustomerRange(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoCustomerDiseaseWordView> customerDiseaseWord(GaoCustomerDiseaseWordReq req) {
|
||||
TimiException.required(req, "not found req");
|
||||
TimiException.required(req.getStoreId(), "not found req.storeId");
|
||||
|
||||
int topN = normalizeTopN(req.getTopN());
|
||||
Map<String, Long> wordCountMap = new HashMap<>();
|
||||
for (GaoCustomerDiseaseTextView customer : customerMapper.selectDiseaseTextByStoreId(req.getStoreId())) {
|
||||
countNounWord(customer.getDisease(), wordCountMap);
|
||||
countNounWord(customer.getRemark(), wordCountMap);
|
||||
}
|
||||
return wordCountMap.entrySet().stream()
|
||||
.sorted(Map.Entry.<String, Long>comparingByValue().reversed().thenComparing(Map.Entry.comparingByKey()))
|
||||
.limit(topN)
|
||||
.map(item -> new GaoCustomerDiseaseWordView(item.getKey(), item.getValue()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private void countNounWord(String text, Map<String, Long> wordCountMap) {
|
||||
if (text == null || text.isBlank()) {
|
||||
return;
|
||||
}
|
||||
for (Term term : StandardTokenizer.segment(text)) {
|
||||
if (term.nature != null && term.nature.toString().startsWith("n")) {
|
||||
wordCountMap.merge(term.word, 1L, Long::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int normalizeTopN(Integer topN) {
|
||||
if (topN == null) {
|
||||
return 60;
|
||||
}
|
||||
return Math.clamp(topN, 1, 100);
|
||||
}
|
||||
|
||||
private void checkRangeReq(GaoCustomerChartReq req) {
|
||||
TimiException.required(req, "not found req");
|
||||
TimiException.required(req.getStoreId(), "not found req.storeId");
|
||||
|
||||
+13
@@ -14,7 +14,9 @@ import com.imyeyu.api.modules.gao.service.GaoCustomerService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoEventRecordService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoPointAccountService;
|
||||
import com.imyeyu.api.modules.gao.util.CustomerXSSFBuilder;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerAgeStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateCustomerView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerPage;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq;
|
||||
@@ -410,6 +412,10 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
|
||||
for (GaoCustomerDailyStateView view : mapper.stateDailyNewCustomer(req.getStoreId(), beginCreatedAt, endCreatedAt)) {
|
||||
newCustomerCountMap.put(view.getDateValue(), TimiJava.defaultIfNull(view.getNewCustomerCount(), 0L));
|
||||
}
|
||||
Map<Integer, List<GaoCustomerDailyStateCustomerView>> customerListMap = new HashMap<>();
|
||||
for (GaoCustomerDailyStateCustomerView customer : mapper.selectDailyNewCustomer(req.getStoreId(), beginCreatedAt, endCreatedAt)) {
|
||||
customerListMap.computeIfAbsent(customer.getDateValue(), key -> new ArrayList<>()).add(customer);
|
||||
}
|
||||
long totalCustomerCount = TimiJava.defaultIfNull(mapper.countCreatedBefore(req.getStoreId(), beginCreatedAt), 0L);
|
||||
List<GaoCustomerDailyStateView> list = new ArrayList<>();
|
||||
for (LocalDate date = beginDate; !date.isAfter(endDate); date = date.plusDays(1)) {
|
||||
@@ -420,6 +426,7 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
|
||||
view.setDateValue(dateValue);
|
||||
view.setNewCustomerCount(newCustomerCount);
|
||||
view.setTotalCustomerCount(totalCustomerCount);
|
||||
view.setCustomerList(customerListMap.getOrDefault(dateValue, List.of()));
|
||||
list.add(view);
|
||||
}
|
||||
return list;
|
||||
@@ -431,6 +438,12 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
|
||||
return mapper.stateGender(storeId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoCustomerAgeStatView> stateAge(String storeId) {
|
||||
TimiException.required(storeId, "not found storeId");
|
||||
return mapper.stateAge(storeId);
|
||||
}
|
||||
|
||||
private void changeInvitedCount(String customerId, long delta) {
|
||||
if (TimiJava.isEmpty(customerId) || delta == 0) {
|
||||
return;
|
||||
|
||||
+21
-1
@@ -450,12 +450,32 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
|
||||
GaoSalesOrderItem.ItemType expectedItemType = order.getOrderType() == GaoSalesOrder.OrderType.PACKAGE
|
||||
? GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT : GaoSalesOrderItem.ItemType.DIRECT_PRODUCT;
|
||||
TimiException.requiredTrue(source.getItemType() == expectedItemType, "订单项类型与订单类型不匹配");
|
||||
GaoProductSkuUnitSelection selection = skuService.getSellableSkuUnit(order.getStoreId(), source.getSkuId(), requestItem.getSkuUnitId());
|
||||
if (requestItem.getSkuUnitId().equals(source.getSkuUnitId())) {
|
||||
result.add(toSourceByOrderSnapshot(order, source, requestItem.getQuantity(), checkAvailable));
|
||||
continue;
|
||||
}
|
||||
GaoProductSkuUnitSelection selection = skuService.getFulfillableSkuUnit(order.getStoreId(), source.getSkuId(), requestItem.getSkuUnitId());
|
||||
result.add(toSource(order, source, selection.getProduct(), selection.getSku(), selection.getUnit(), requestItem.getQuantity(), checkAvailable));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private SourceEntry toSourceByOrderSnapshot(GaoSalesOrder order, GaoSalesOrderItem source, BigDecimal quantity, boolean checkAvailable) {
|
||||
GaoProductSku sku = skuService.get(source.getSkuId());
|
||||
TimiException.required(sku, "履约来源 SKU 不存在");
|
||||
GaoProduct product = productService.get(sku.getProductId());
|
||||
TimiException.required(product, "履约来源产品不存在");
|
||||
TimiException.requiredTrue(order.getStoreId().equals(product.getStoreId()), "履约来源产品不属于当前门店");
|
||||
quantity = requiredPositiveQuantity(quantity, "履约数量");
|
||||
if (!Boolean.TRUE.equals(sku.getAllowDecimalQuantity())) {
|
||||
TimiException.requiredTrue(quantity.stripTrailingZeros().scale() <= 0, "该产品不允许小数数量");
|
||||
}
|
||||
BigDecimal baseQuantityPerUnit = requiredBaseQuantityPerUnit(source.getBaseQuantityPerUnitSnapshot());
|
||||
BigDecimal baseQuantity = toBaseQuantity(quantity, baseQuantityPerUnit, "履约基础数量最多保留三位小数");
|
||||
return buildSource(order, source, source.getSkuUnitId(), source.getUnitSnapshot(), baseQuantityPerUnit, quantity,
|
||||
baseQuantity, product, sku, checkAvailable);
|
||||
}
|
||||
|
||||
private List<SourceEntry> loadPersistedSources(GaoSalesOrder order, List<GaoOrderFulfillmentItem> itemList) {
|
||||
TimiException.requiredTrue(itemList != null && !itemList.isEmpty(), "履约单没有明细");
|
||||
List<GaoOrderFulfillmentItem> sortedItemList = new ArrayList<>(itemList);
|
||||
|
||||
+18
-5
@@ -109,6 +109,22 @@ public class GaoProductSkuServiceImplement extends AbstractEntityService<GaoProd
|
||||
return getSkuUnit(storeId, skuId, skuUnitId, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaoProductSkuUnitSelection getFulfillableSkuUnit(String storeId, String skuId, String skuUnitId) {
|
||||
TimiException.required(storeId, "门店 ID 不能为空");
|
||||
TimiException.required(skuId, "SKU ID 不能为空");
|
||||
TimiException.required(skuUnitId, "SKU 单位 ID 不能为空");
|
||||
GaoProductSku sku = mapper.selectForUpdate(skuId);
|
||||
TimiException.required(sku, "SKU 不存在");
|
||||
TimiException.requiredTrue(storeId.equals(sku.getStoreId()), "SKU 不属于当前门店");
|
||||
GaoProduct product = productServiceProvider.getObject().get(sku.getProductId());
|
||||
TimiException.required(product, "产品不存在");
|
||||
GaoProductSkuUnit unit = unitMapper.selectForUpdate(skuUnitId);
|
||||
TimiException.required(unit, "SKU 单位不存在");
|
||||
TimiException.requiredTrue(skuId.equals(unit.getSkuId()), "SKU 单位不属于当前 SKU");
|
||||
return new GaoProductSkuUnitSelection(product, sku, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaoProductSkuUnitSelection getSkuUnit(String storeId, String skuId, String skuUnitId, boolean purchase) {
|
||||
TimiException.required(storeId, "not found storeId");
|
||||
@@ -354,11 +370,8 @@ public class GaoProductSkuServiceImplement extends AbstractEntityService<GaoProd
|
||||
TimiException.requiredTrue(1 == unitMapper.updateUnit(unit), "SKU 单位更新失败");
|
||||
}
|
||||
}
|
||||
for (GaoProductSkuUnit existing : existingUnitList) {
|
||||
if (!retainedIdSet.contains(existing.getId())) {
|
||||
unitMapper.delete(existing.getId());
|
||||
}
|
||||
}
|
||||
TimiException.requiredTrue(existingUnitList.stream().allMatch(existing -> retainedIdSet.contains(existing.getId())),
|
||||
"已保存的 SKU 单位不能删除,请关闭允许销售和允许采购");
|
||||
}
|
||||
|
||||
private void saveSpecValues(String skuId, List<String> specValueIdList) {
|
||||
|
||||
+7
@@ -3,6 +3,7 @@ package com.imyeyu.api.modules.gao.service.implement;
|
||||
import com.imyeyu.api.modules.gao.service.GaoCustomerService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoEventRecordService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoStoreChartService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerAgeStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq;
|
||||
@@ -43,6 +44,12 @@ public class GaoStoreChartServiceImplement implements GaoStoreChartService {
|
||||
return customerService.stateGender(storeId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoCustomerAgeStatView> customerAge(String storeId) {
|
||||
TimiException.required(storeId, "not found storeId");
|
||||
return customerService.stateAge(storeId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoEventRecordDailyStatView> eventRecordDailyStat(GaoStoreChartReq req) {
|
||||
checkRangeReq(req);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
///
|
||||
/// 客户年龄统计视图
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-11
|
||||
@Data
|
||||
public class GaoCustomerAgeStatView {
|
||||
|
||||
/// 客户年龄
|
||||
private Long age;
|
||||
|
||||
/// 该年龄的客户数量
|
||||
private Long count;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
|
||||
///
|
||||
/// 客户每日统计中的客户摘要
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-11
|
||||
@Data
|
||||
public class GaoCustomerDailyStateCustomerView {
|
||||
|
||||
/// 用于服务层按日期分组,不返回给前端
|
||||
@JsonIgnore
|
||||
private Integer dateValue;
|
||||
|
||||
/// 客户姓名
|
||||
private String name;
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
///
|
||||
/// 客户每日统计视图
|
||||
///
|
||||
@@ -18,4 +20,7 @@ public class GaoCustomerDailyStateView {
|
||||
|
||||
/// 截止当日客户总数
|
||||
private Long totalCustomerCount;
|
||||
|
||||
/// 当日新增客户列表
|
||||
private List<GaoCustomerDailyStateCustomerView> customerList;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/// 客户病症原始文本
|
||||
///
|
||||
/// 只用于后端分词,不对外返回。
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-11
|
||||
@Data
|
||||
public class GaoCustomerDiseaseTextView {
|
||||
|
||||
/// 备注
|
||||
private String remark;
|
||||
|
||||
/// 病症
|
||||
private String disease;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/// 客户病症词频查询请求
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-11
|
||||
@Data
|
||||
public class GaoCustomerDiseaseWordReq {
|
||||
|
||||
/// 门店 ID
|
||||
private String storeId;
|
||||
|
||||
/// 返回词语数量
|
||||
private Integer topN;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/// 客户病症词频
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-11
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class GaoCustomerDiseaseWordView {
|
||||
|
||||
/// 词语
|
||||
private String word;
|
||||
|
||||
/// 词语出现次数
|
||||
private Long value;
|
||||
}
|
||||
@@ -23,4 +23,10 @@ public class GaoCustomerPage extends Page<GaoCustomer> {
|
||||
|
||||
/// 标签 ID 列表,客户必须同时拥有列表内全部标签
|
||||
private List<String> tagIdList;
|
||||
|
||||
/// 录入时间起始,包含,Unix 毫秒时间戳
|
||||
private Long beginCreatedAt;
|
||||
|
||||
/// 录入时间结束,包含,Unix 毫秒时间戳
|
||||
private Long endCreatedAt;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,12 @@
|
||||
HAVING COUNT(DISTINCT `ta`.`tag_id`) = #{tagCount}
|
||||
)
|
||||
</if>
|
||||
<if test="beginCreatedAt != null">
|
||||
AND `c`.`created_at` >= #{beginCreatedAt}
|
||||
</if>
|
||||
<if test="endCreatedAt != null">
|
||||
AND `c`.`created_at` <= #{endCreatedAt}
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
<select id="countByQuery" resultType="long">
|
||||
@@ -92,6 +98,19 @@
|
||||
ORDER BY `dateValue` ASC
|
||||
</select>
|
||||
|
||||
<select id="selectDailyNewCustomer" resultType="com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateCustomerView">
|
||||
SELECT
|
||||
`name`,
|
||||
CAST(DATE_FORMAT(FROM_UNIXTIME(`created_at` / 1000), '%Y%m%d') AS UNSIGNED) AS `dateValue`
|
||||
FROM `gao_customer`
|
||||
WHERE
|
||||
`store_id` = #{storeId}
|
||||
AND `created_at` >= #{beginCreatedAt}
|
||||
AND `created_at` <= #{endCreatedAt}
|
||||
AND `deleted_at` IS NULL
|
||||
ORDER BY `dateValue` ASC, `created_at` ASC, `id` ASC
|
||||
</select>
|
||||
|
||||
<select id="countCreatedBefore" resultType="java.lang.Long">
|
||||
SELECT
|
||||
COUNT(1)
|
||||
@@ -112,4 +131,31 @@
|
||||
AND `deleted_at` IS NULL
|
||||
GROUP BY IFNULL(CAST(`gender` AS CHAR), 'UNKNOWN')
|
||||
</select>
|
||||
|
||||
<select id="stateAge" resultType="com.imyeyu.api.modules.gao.vo.GaoCustomerAgeStatView">
|
||||
SELECT
|
||||
COALESCE(`age`, TIMESTAMPDIFF(YEAR, `birthdate`, CURDATE())) AS `age`,
|
||||
COUNT(1) AS `count`
|
||||
FROM `gao_customer`
|
||||
WHERE
|
||||
`store_id` = #{storeId}
|
||||
AND `deleted_at` IS NULL
|
||||
AND (`age` IS NOT NULL OR `birthdate` IS NOT NULL)
|
||||
GROUP BY COALESCE(`age`, TIMESTAMPDIFF(YEAR, `birthdate`, CURDATE()))
|
||||
ORDER BY COALESCE(`age`, TIMESTAMPDIFF(YEAR, `birthdate`, CURDATE())) ASC
|
||||
</select>
|
||||
|
||||
<select id="selectDiseaseTextByStoreId" resultType="com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseTextView">
|
||||
SELECT
|
||||
`remark`,
|
||||
`disease`
|
||||
FROM `gao_customer`
|
||||
WHERE
|
||||
`store_id` = #{storeId}
|
||||
AND `deleted_at` IS NULL
|
||||
AND (
|
||||
(`remark` IS NOT NULL AND `remark` != '')
|
||||
OR (`disease` IS NOT NULL AND `disease` != '')
|
||||
)
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@@ -348,10 +348,23 @@
|
||||
FROM `daily_stat` d
|
||||
JOIN `open_calendar` o ON d.`registeredDateValue` = o.`dateValue`
|
||||
),
|
||||
`customer_stat` AS (
|
||||
SELECT
|
||||
d.`customerId`,
|
||||
MAX(CASE WHEN d.`registeredDateValue` = #{todayDateValue} THEN 1 ELSE 0 END) AS `hasToday`
|
||||
FROM `daily_stat` d
|
||||
GROUP BY d.`customerId`
|
||||
),
|
||||
`anchor_date` AS (
|
||||
SELECT
|
||||
MAX(`openIndex`) AS `anchorOpenIndex`
|
||||
FROM `open_calendar`
|
||||
s.`customerId`,
|
||||
CASE
|
||||
WHEN s.`hasToday` = 1 THEN MAX(CASE WHEN c.`dateValue` = #{todayDateValue} THEN c.`openIndex` END)
|
||||
ELSE MAX(CASE WHEN c.`dateValue` < #{todayDateValue} THEN c.`openIndex` END)
|
||||
END AS `anchorOpenIndex`
|
||||
FROM `customer_stat` s
|
||||
CROSS JOIN `open_calendar` c
|
||||
GROUP BY s.`customerId`, s.`hasToday`
|
||||
),
|
||||
`continuous_stat` AS (
|
||||
SELECT
|
||||
@@ -361,7 +374,7 @@
|
||||
ELSE COUNT(1)
|
||||
END AS `recentContinuousEventDays`
|
||||
FROM `continuous_ordered` o
|
||||
JOIN `anchor_date` a
|
||||
JOIN `anchor_date` a ON o.`customerId` = a.`customerId`
|
||||
WHERE
|
||||
o.`openIndex` + o.`dayIndex` = a.`anchorOpenIndex` + 1
|
||||
<if test="eventIdList == null or eventIdList.size() != 1">
|
||||
|
||||
Reference in New Issue
Block a user