zhouhui.jiang

添加数据验证

Showing 48 changed files with 1993 additions and 8 deletions
...@@ -183,3 +183,4 @@ public class DeliveryMainController { ...@@ -183,3 +183,4 @@ public class DeliveryMainController {
183 } 183 }
184 184
185 185
186 +
......
...@@ -186,3 +186,4 @@ public class InvoiceMainController { ...@@ -186,3 +186,4 @@ public class InvoiceMainController {
186 } 186 }
187 187
188 188
189 +
......
...@@ -62,3 +62,4 @@ public class DeliveryAddReq { ...@@ -62,3 +62,4 @@ public class DeliveryAddReq {
62 } 62 }
63 63
64 64
65 +
......
...@@ -38,3 +38,4 @@ public class DeliveryItemAddReq { ...@@ -38,3 +38,4 @@ public class DeliveryItemAddReq {
38 } 38 }
39 39
40 40
41 +
......
...@@ -60,3 +60,4 @@ public class DeliveryItemRes { ...@@ -60,3 +60,4 @@ public class DeliveryItemRes {
60 } 60 }
61 61
62 62
63 +
......
...@@ -41,3 +41,4 @@ public class DeliveryItemUpdateReq { ...@@ -41,3 +41,4 @@ public class DeliveryItemUpdateReq {
41 } 41 }
42 42
43 43
44 +
......
...@@ -68,3 +68,4 @@ public class DeliveryRes { ...@@ -68,3 +68,4 @@ public class DeliveryRes {
68 } 68 }
69 69
70 70
71 +
......
...@@ -66,3 +66,4 @@ public class DeliveryUpdateReq { ...@@ -66,3 +66,4 @@ public class DeliveryUpdateReq {
66 } 66 }
67 67
68 68
69 +
......
...@@ -43,3 +43,4 @@ public class InvoiceItemAddReq { ...@@ -43,3 +43,4 @@ public class InvoiceItemAddReq {
43 } 43 }
44 44
45 45
46 +
......
...@@ -63,3 +63,4 @@ public class InvoiceItemRes { ...@@ -63,3 +63,4 @@ public class InvoiceItemRes {
63 } 63 }
64 64
65 65
66 +
......
...@@ -46,3 +46,4 @@ public class InvoiceItemUpdateReq { ...@@ -46,3 +46,4 @@ public class InvoiceItemUpdateReq {
46 } 46 }
47 47
48 48
49 +
......
...@@ -56,3 +56,4 @@ public class InvoiceQueryReq { ...@@ -56,3 +56,4 @@ public class InvoiceQueryReq {
56 } 56 }
57 57
58 58
59 +
......
...@@ -67,3 +67,4 @@ public class DeliveryItem { ...@@ -67,3 +67,4 @@ public class DeliveryItem {
67 } 67 }
68 68
69 69
70 +
......
...@@ -3,6 +3,7 @@ package com.apple.erp.entity; ...@@ -3,6 +3,7 @@ package com.apple.erp.entity;
3 import com.baomidou.mybatisplus.annotation.IdType; 3 import com.baomidou.mybatisplus.annotation.IdType;
4 import com.baomidou.mybatisplus.annotation.TableId; 4 import com.baomidou.mybatisplus.annotation.TableId;
5 import com.baomidou.mybatisplus.annotation.TableName; 5 import com.baomidou.mybatisplus.annotation.TableName;
6 +import com.baomidou.mybatisplus.annotation.TableField;
6 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 8 import lombok.Data;
8 import lombok.EqualsAndHashCode; 9 import lombok.EqualsAndHashCode;
...@@ -28,6 +29,10 @@ public class ExceptionWorkorder { ...@@ -28,6 +29,10 @@ public class ExceptionWorkorder {
28 @Schema(description = "工单编号") 29 @Schema(description = "工单编号")
29 private String workorderNo; 30 private String workorderNo;
30 31
32 + @Schema(description = "当日序列号,用于生成工单号")
33 + @TableField("`index`")
34 + private Integer index;
35 +
31 @Schema(description = "关联订单编号") 36 @Schema(description = "关联订单编号")
32 private String orderNo; 37 private String orderNo;
33 38
......
...@@ -73,3 +73,4 @@ public class InvoiceItem { ...@@ -73,3 +73,4 @@ public class InvoiceItem {
73 } 73 }
74 74
75 75
76 +
......
...@@ -82,3 +82,4 @@ public class InvoiceMain { ...@@ -82,3 +82,4 @@ public class InvoiceMain {
82 } 82 }
83 83
84 84
85 +
......
...@@ -79,6 +79,12 @@ public class Rebate { ...@@ -79,6 +79,12 @@ public class Rebate {
79 private Integer calcFlag; 79 private Integer calcFlag;
80 80
81 /** 81 /**
82 + * 序列号索引(用于生成RebateNo)
83 + */
84 + @TableField("t_index")
85 + private Integer tIndex;
86 +
87 + /**
82 * 数据上传时间 88 * 数据上传时间
83 */ 89 */
84 @TableField("upload_time") 90 @TableField("upload_time")
......
1 +package com.apple.erp.exception;
2 +
3 +/**
4 + * 经销商不匹配异常
5 + * 当出库单中的经销商与关联订单中的经销商不一致时抛出此异常
6 + *
7 + * @author Apple ERP System
8 + * @since 2025-01-01
9 + */
10 +public class DealerMismatchException extends RuntimeException {
11 +
12 + public DealerMismatchException(String message) {
13 + super(message);
14 + }
15 +
16 + public DealerMismatchException(String message, Throwable cause) {
17 + super(message, cause);
18 + }
19 +}
1 +package com.apple.erp.exception;
2 +
3 +/**
4 + * 发票验证异常
5 + * 当发票中的经销商与关联订单或出库单中的经销商不一致时抛出此异常
6 + *
7 + * @author Apple ERP System
8 + * @since 2025-01-01
9 + */
10 +public class InvoiceValidationException extends RuntimeException {
11 +
12 + public InvoiceValidationException(String message) {
13 + super(message);
14 + }
15 +
16 + public InvoiceValidationException(String message, Throwable cause) {
17 + super(message, cause);
18 + }
19 +}
...@@ -15,3 +15,4 @@ public interface DeliveryItemMapper extends BaseMapper<DeliveryItem> { ...@@ -15,3 +15,4 @@ public interface DeliveryItemMapper extends BaseMapper<DeliveryItem> {
15 } 15 }
16 16
17 17
18 +
......
...@@ -3,6 +3,8 @@ package com.apple.erp.mapper; ...@@ -3,6 +3,8 @@ package com.apple.erp.mapper;
3 import com.apple.erp.entity.DeliveryMain; 3 import com.apple.erp.entity.DeliveryMain;
4 import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 import org.apache.ibatis.annotations.Mapper; 5 import org.apache.ibatis.annotations.Mapper;
6 +import org.apache.ibatis.annotations.Param;
7 +import org.apache.ibatis.annotations.Select;
6 8
7 /** 9 /**
8 * 出库主表Mapper接口 10 * 出库主表Mapper接口
...@@ -12,6 +14,16 @@ import org.apache.ibatis.annotations.Mapper; ...@@ -12,6 +14,16 @@ import org.apache.ibatis.annotations.Mapper;
12 */ 14 */
13 @Mapper 15 @Mapper
14 public interface DeliveryMainMapper extends BaseMapper<DeliveryMain> { 16 public interface DeliveryMainMapper extends BaseMapper<DeliveryMain> {
17 +
18 + /**
19 + * 根据出库单号查询出库单
20 + *
21 + * @param deliveryNo 出库单号
22 + * @return 出库单信息
23 + */
24 + @Select("SELECT * FROM t_delivery_main WHERE delivery_no = #{deliveryNo} AND del_flag = '0'")
25 + DeliveryMain selectByDeliveryNo(@Param("deliveryNo") String deliveryNo);
15 } 26 }
16 27
17 28
29 +
......
...@@ -7,7 +7,9 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; ...@@ -7,7 +7,9 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
7 import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 7 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
8 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
9 import org.apache.ibatis.annotations.Param; 9 import org.apache.ibatis.annotations.Param;
10 +import org.apache.ibatis.annotations.Select;
10 11
12 +import java.time.LocalDate;
11 import java.util.List; 13 import java.util.List;
12 14
13 /** 15 /**
...@@ -62,4 +64,13 @@ public interface ExceptionWorkorderMapper extends BaseMapper<ExceptionWorkorder> ...@@ -62,4 +64,13 @@ public interface ExceptionWorkorderMapper extends BaseMapper<ExceptionWorkorder>
62 * @return 异常工单列表(不分页) 64 * @return 异常工单列表(不分页)
63 */ 65 */
64 List<ExceptionWorkorderRes> selectExceptionWorkorderListForExport(@Param("query") ExceptionWorkorderQueryReq queryReq); 66 List<ExceptionWorkorderRes> selectExceptionWorkorderListForExport(@Param("query") ExceptionWorkorderQueryReq queryReq);
67 +
68 + /**
69 + * 获取指定日期的最大序列号
70 + *
71 + * @param date 日期
72 + * @return 最大序列号,如果没有记录则返回0
73 + */
74 + @Select("SELECT COALESCE(MAX(`index`), 0) FROM t_exception_workorder WHERE DATE(create_time) = #{date} AND del_flag = '0'")
75 + Integer getMaxIndexByDate(@Param("date") LocalDate date);
65 } 76 }
......
...@@ -15,3 +15,4 @@ public interface InvoiceItemMapper extends BaseMapper<InvoiceItem> { ...@@ -15,3 +15,4 @@ public interface InvoiceItemMapper extends BaseMapper<InvoiceItem> {
15 } 15 }
16 16
17 17
18 +
......
...@@ -15,3 +15,4 @@ public interface InvoiceMainMapper extends BaseMapper<InvoiceMain> { ...@@ -15,3 +15,4 @@ public interface InvoiceMainMapper extends BaseMapper<InvoiceMain> {
15 } 15 }
16 16
17 17
18 +
......
...@@ -3,6 +3,10 @@ package com.apple.erp.mapper; ...@@ -3,6 +3,10 @@ package com.apple.erp.mapper;
3 import com.apple.erp.entity.OrderMain; 3 import com.apple.erp.entity.OrderMain;
4 import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 import org.apache.ibatis.annotations.Mapper; 5 import org.apache.ibatis.annotations.Mapper;
6 +import org.apache.ibatis.annotations.Param;
7 +import org.apache.ibatis.annotations.Select;
8 +
9 +import java.time.LocalDateTime;
6 10
7 /** 11 /**
8 * 订单主表Mapper接口 12 * 订单主表Mapper接口
...@@ -12,5 +16,23 @@ import org.apache.ibatis.annotations.Mapper; ...@@ -12,5 +16,23 @@ import org.apache.ibatis.annotations.Mapper;
12 */ 16 */
13 @Mapper 17 @Mapper
14 public interface OrderMainMapper extends BaseMapper<OrderMain> { 18 public interface OrderMainMapper extends BaseMapper<OrderMain> {
19 +
20 + /**
21 + * 根据订单号查询订单
22 + *
23 + * @param orderNo 订单号
24 + * @return 订单信息
25 + */
26 + @Select("SELECT * FROM t_order_main WHERE order_no = #{orderNo} AND del_flag = '0'")
27 + OrderMain selectByOrderNo(@Param("orderNo") String orderNo);
28 +
29 + /**
30 + * 获取订单的最新出库时间
31 + *
32 + * @param orderNo 订单号
33 + * @return 最新出库时间
34 + */
35 + @Select("SELECT MAX(delivery_date) FROM t_delivery_main WHERE order_no = #{orderNo} AND del_flag = '0'")
36 + LocalDateTime getLatestDeliveryTimeByOrderNo(@Param("orderNo") String orderNo);
15 } 37 }
16 38
......
...@@ -71,6 +71,14 @@ public interface RebateMapper extends BaseMapper<Rebate> { ...@@ -71,6 +71,14 @@ public interface RebateMapper extends BaseMapper<Rebate> {
71 Rebate selectByRebateNo(@Param("rebateNo") String rebateNo); 71 Rebate selectByRebateNo(@Param("rebateNo") String rebateNo);
72 72
73 /** 73 /**
74 + * 获取指定日期的最大序列号
75 + *
76 + * @param rebateDate 返利日期
77 + * @return 最大序列号
78 + */
79 + Integer getMaxIndexByDate(@Param("rebateDate") String rebateDate);
80 +
81 + /**
74 * 获取返利月度统计数据 82 * 获取返利月度统计数据
75 * 83 *
76 * @param year 年份 84 * @param year 年份
......
...@@ -84,3 +84,4 @@ public interface DeliveryMainService extends IService<DeliveryMain> { ...@@ -84,3 +84,4 @@ public interface DeliveryMainService extends IService<DeliveryMain> {
84 } 84 }
85 85
86 86
87 +
......
...@@ -84,3 +84,4 @@ public interface InvoiceMainService extends IService<InvoiceMain> { ...@@ -84,3 +84,4 @@ public interface InvoiceMainService extends IService<InvoiceMain> {
84 } 84 }
85 85
86 86
87 +
......
1 +package com.apple.erp.service;
2 +
3 +/**
4 + * 返利结转服务
5 + */
6 +public interface RebateSettlementService {
7 +
8 + /**
9 + * 若订单已出库且已开票,且未计算,则扣减经销商待返利并生成台账,并将订单标记为已计算。
10 + *
11 + * @param orderNo 订单编号
12 + * @param dealerCode 经销商编码
13 + * @param dealerName 经销商名称
14 + */
15 + void settleIfReady(String orderNo, String dealerCode, String dealerName);
16 +}
17 +
18 +
...@@ -3,8 +3,13 @@ package com.apple.erp.service.impl; ...@@ -3,8 +3,13 @@ package com.apple.erp.service.impl;
3 import com.apple.erp.dto.*; 3 import com.apple.erp.dto.*;
4 import com.apple.erp.entity.DeliveryItem; 4 import com.apple.erp.entity.DeliveryItem;
5 import com.apple.erp.entity.OrderMain; 5 import com.apple.erp.entity.OrderMain;
6 +// rebate settlement handled by RebateSettlementService
7 +import com.apple.erp.service.RebateSettlementService;
6 import com.apple.erp.mapper.DeliveryItemMapper; 8 import com.apple.erp.mapper.DeliveryItemMapper;
7 import com.apple.erp.mapper.OrderMainMapper; 9 import com.apple.erp.mapper.OrderMainMapper;
10 +// removed unused mapper imports after delegating settlement logic
11 +import com.apple.erp.util.TimingValidationUtil;
12 +import com.apple.erp.exception.DealerMismatchException;
8 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 13 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
9 import com.baomidou.mybatisplus.core.metadata.IPage; 14 import com.baomidou.mybatisplus.core.metadata.IPage;
10 import com.baomidou.mybatisplus.core.toolkit.Wrappers; 15 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
...@@ -42,6 +47,14 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del ...@@ -42,6 +47,14 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
42 @Autowired 47 @Autowired
43 private OrderMainMapper orderMainMapper; 48 private OrderMainMapper orderMainMapper;
44 49
50 + @Autowired
51 + private TimingValidationUtil timingValidationUtil;
52 +
53 + // rebate settlement is delegated; no local mapper usage required
54 +
55 + @Autowired
56 + private RebateSettlementService rebateSettlementService;
57 +
45 private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 58 private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
46 59
47 @Override 60 @Override
...@@ -118,11 +131,37 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del ...@@ -118,11 +131,37 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
118 throw new IllegalArgumentException("关联的订单编号不存在"); 131 throw new IllegalArgumentException("关联的订单编号不存在");
119 } 132 }
120 133
134 + // 验证出库单中的经销商与关联订单中的经销商是否一致
135 + validateDealerConsistency(addReq.getOrderNo(), addReq.getDealerCode());
136 +
137 + boolean isValid = true;
138 + // 时序逻辑验证:出库时间不得晚于订单创建时间
139 + if (StringUtils.hasText(addReq.getDeliveryDate())) {
140 + LocalDateTime deliveryTime = parseDateTime(addReq.getDeliveryDate());
141 + TimingValidationUtil.ValidationResult validationResult =
142 + timingValidationUtil.validateDeliveryTiming(addReq.getOrderNo(), deliveryTime);
143 + isValid = validationResult.isValid();
144 + if (!validationResult.isValid()) {
145 + // 创建异常工单
146 + Long workorderId = timingValidationUtil.createExceptionWorkorder(
147 + addReq.getOrderNo(),
148 + addReq.getDealerCode(),
149 + addReq.getDealerName(),
150 + validationResult.getExceptionType(),
151 + validationResult.getExceptionDescription(),
152 + "DELIVERY",
153 + null // 出库单ID在插入后设置
154 + );
155 + log.warn("出库单时序验证失败,已创建异常工单[{}]: {}", workorderId, validationResult.getExceptionDescription());
156 + // 继续执行出库单创建,但记录异常
157 + }
158 + }
159 +
121 DeliveryMain deliveryMain = new DeliveryMain(); 160 DeliveryMain deliveryMain = new DeliveryMain();
122 BeanUtils.copyProperties(addReq, deliveryMain); 161 BeanUtils.copyProperties(addReq, deliveryMain);
123 deliveryMain.setCreateTime(LocalDateTime.now()); 162 deliveryMain.setCreateTime(LocalDateTime.now());
124 deliveryMain.setUpdateTime(LocalDateTime.now()); 163 deliveryMain.setUpdateTime(LocalDateTime.now());
125 - if(StringUtils.hasText(addReq.getDeliveryDate())) { 164 + if (StringUtils.hasText(addReq.getDeliveryDate())) {
126 deliveryMain.setDeliveryDate(parseDateTime(addReq.getDeliveryDate())); 165 deliveryMain.setDeliveryDate(parseDateTime(addReq.getDeliveryDate()));
127 } 166 }
128 deliveryMain.setDelFlag("0"); 167 deliveryMain.setDelFlag("0");
...@@ -139,6 +178,12 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del ...@@ -139,6 +178,12 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
139 deliveryItem.setDelFlag("0"); 178 deliveryItem.setDelFlag("0");
140 deliveryItemMapper.insert(deliveryItem); 179 deliveryItemMapper.insert(deliveryItem);
141 } 180 }
181 + if (isValid) {
182 + // 更新关联订单的出库状态为已出库(1)
183 + updateOrderDeliveryStatusToShipped(deliveryMain.getOrderNo());
184 + }
185 + // 方法末尾:结转返利(若订单已出库且已开票且未计算)
186 + rebateSettlementService.settleIfReady(deliveryMain.getOrderNo(), deliveryMain.getDealerCode(), deliveryMain.getDealerName());
142 return true; 187 return true;
143 } 188 }
144 return false; 189 return false;
...@@ -157,6 +202,34 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del ...@@ -157,6 +202,34 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
157 throw new IllegalArgumentException("关联的订单编号不存在"); 202 throw new IllegalArgumentException("关联的订单编号不存在");
158 } 203 }
159 204
205 + // 验证出库单中的经销商与关联订单中的经销商是否一致
206 + validateDealerConsistency(updateReq.getOrderNo(), updateReq.getDealerCode());
207 +
208 + boolean isValid = true;
209 + // 时序逻辑验证:出库时间不得晚于订单创建时间
210 + if (updateReq.getDeliveryDate() != null) {
211 + LocalDateTime deliveryTime = updateReq.getDeliveryDate();
212 + TimingValidationUtil.ValidationResult validationResult =
213 + timingValidationUtil.validateDeliveryTiming(updateReq.getOrderNo(), deliveryTime);
214 + isValid = validationResult.isValid();
215 + if (!validationResult.isValid()) {
216 + // 创建异常工单
217 + Long workorderId = timingValidationUtil.createExceptionWorkorder(
218 + updateReq.getOrderNo(),
219 + updateReq.getDealerCode(),
220 + updateReq.getDealerName(),
221 + validationResult.getExceptionType(),
222 + validationResult.getExceptionDescription(),
223 + "DELIVERY",
224 + updateReq.getDeliveryId()
225 + );
226 +
227 + log.warn("出库单修改时序验证失败,已创建异常工单[{}]: {}", workorderId, validationResult.getExceptionDescription());
228 +
229 + // 继续执行出库单修改,但记录异常
230 + }
231 + }
232 +
160 DeliveryMain deliveryMain = new DeliveryMain(); 233 DeliveryMain deliveryMain = new DeliveryMain();
161 BeanUtils.copyProperties(updateReq, deliveryMain); 234 BeanUtils.copyProperties(updateReq, deliveryMain);
162 deliveryMain.setUpdateTime(LocalDateTime.now()); 235 deliveryMain.setUpdateTime(LocalDateTime.now());
...@@ -179,11 +252,39 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del ...@@ -179,11 +252,39 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
179 deliveryItem.setDelFlag("0"); 252 deliveryItem.setDelFlag("0");
180 deliveryItemMapper.insert(deliveryItem); 253 deliveryItemMapper.insert(deliveryItem);
181 } 254 }
255 +
256 + if(isValid)
257 + {
258 + // 订单主表状态(根据订单编号)
259 + updateOrderDeliveryStatusToShipped(updateReq.getOrderNo());
260 + }
261 + // 方法末尾:结转返利(若订单已出库且已开票且未计算)
262 + rebateSettlementService.settleIfReady(updateReq.getOrderNo(), updateReq.getDealerCode(), updateReq.getDealerName());
182 return true; 263 return true;
183 } 264 }
184 return false; 265 return false;
185 } 266 }
186 267
268 + /**
269 + * 将关联订单的出库状态置为已出库(1)
270 + *
271 + * @param orderNo 关联订单编号
272 + */
273 + private void updateOrderDeliveryStatusToShipped(String orderNo) {
274 + try {
275 + OrderMain order = orderMainMapper.selectByOrderNo(orderNo);
276 + if (order != null && !"2".equals(order.getDelFlag())) {
277 + order.setDeliveryStatus(1);
278 + order.setUpdateTime(LocalDateTime.now());
279 + orderMainMapper.updateById(order);
280 + }
281 + } catch (Exception e) {
282 + log.warn("更新订单出库状态失败: orderNo={}, err={}", orderNo, e.getMessage());
283 + }
284 + }
285 +
286 + // 结转逻辑已抽取到 RebateSettlementService
287 +
187 @Override 288 @Override
188 @Transactional 289 @Transactional
189 public boolean deleteDelivery(Long deliveryId) { 290 public boolean deleteDelivery(Long deliveryId) {
...@@ -218,7 +319,8 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del ...@@ -218,7 +319,8 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
218 319
219 /** 320 /**
220 * 检查出库单编号是否已存在 321 * 检查出库单编号是否已存在
221 - * @param deliveryNo 出库单编号 322 + *
323 + * @param deliveryNo 出库单编号
222 * @param excludeDeliveryId 排除的出库单ID(用于修改时) 324 * @param excludeDeliveryId 排除的出库单ID(用于修改时)
223 * @return 是否存在 325 * @return 是否存在
224 */ 326 */
...@@ -234,6 +336,7 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del ...@@ -234,6 +336,7 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
234 336
235 /** 337 /**
236 * 检查订单编号是否在订单管理中存在 338 * 检查订单编号是否在订单管理中存在
339 + *
237 * @param orderNo 订单编号 340 * @param orderNo 订单编号
238 * @return 是否存在 341 * @return 是否存在
239 */ 342 */
...@@ -244,6 +347,30 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del ...@@ -244,6 +347,30 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
244 return orderMainMapper.selectCount(queryWrapper) > 0; 347 return orderMainMapper.selectCount(queryWrapper) > 0;
245 } 348 }
246 349
350 + /**
351 + * 验证出库单中的经销商与关联订单中的经销商是否一致
352 + *
353 + * @param orderNo 订单编号
354 + * @param deliveryDealerCode 出库单中的经销商编码
355 + * @throws DealerMismatchException 当经销商不一致时抛出异常
356 + */
357 + private void validateDealerConsistency(String orderNo, String deliveryDealerCode) {
358 + // 根据订单编号查询订单信息
359 + OrderMain order = orderMainMapper.selectByOrderNo(orderNo);
360 + if (order == null) {
361 + throw new IllegalArgumentException("关联的订单不存在");
362 + }
363 +
364 + // 比较经销商编码
365 + if (!deliveryDealerCode.equals(order.getDealerCode())) {
366 + String errorMessage = String.format(
367 + "出库单中的经销商编码[%s]与关联订单[%s]中的经销商编码[%s]不一致",
368 + deliveryDealerCode, orderNo, order.getDealerCode()
369 + );
370 + throw new DealerMismatchException(errorMessage);
371 + }
372 + }
373 +
247 private LocalDateTime parseDateTime(String dateTimeStr) { 374 private LocalDateTime parseDateTime(String dateTimeStr) {
248 if (!StringUtils.hasText(dateTimeStr)) { 375 if (!StringUtils.hasText(dateTimeStr)) {
249 return null; 376 return null;
...@@ -260,18 +387,18 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del ...@@ -260,18 +387,18 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
260 public Integer getPendingDeliveryCount() { 387 public Integer getPendingDeliveryCount() {
261 LambdaQueryWrapper<DeliveryMain> wrapper = new LambdaQueryWrapper<>(); 388 LambdaQueryWrapper<DeliveryMain> wrapper = new LambdaQueryWrapper<>();
262 wrapper.eq(DeliveryMain::getDelFlag, "0") 389 wrapper.eq(DeliveryMain::getDelFlag, "0")
263 - .eq(DeliveryMain::getDeliveryStatus, 0); // 0表示待出库 390 + .eq(DeliveryMain::getDeliveryStatus, 0); // 0表示待出库
264 return Math.toIntExact(count(wrapper)); 391 return Math.toIntExact(count(wrapper));
265 } 392 }
266 - 393 +
267 @Override 394 @Override
268 public List<DeliveryRes> getRecentDeliveries(Integer limit) { 395 public List<DeliveryRes> getRecentDeliveries(Integer limit) {
269 try { 396 try {
270 LambdaQueryWrapper<DeliveryMain> wrapper = new LambdaQueryWrapper<>(); 397 LambdaQueryWrapper<DeliveryMain> wrapper = new LambdaQueryWrapper<>();
271 wrapper.eq(DeliveryMain::getDelFlag, "0") 398 wrapper.eq(DeliveryMain::getDelFlag, "0")
272 - .orderByDesc(DeliveryMain::getCreateTime) 399 + .orderByDesc(DeliveryMain::getCreateTime)
273 - .last("LIMIT " + (limit != null ? limit : 5)); 400 + .last("LIMIT " + (limit != null ? limit : 5));
274 - 401 +
275 List<DeliveryMain> deliveries = list(wrapper); 402 List<DeliveryMain> deliveries = list(wrapper);
276 return deliveries.stream().map(delivery -> { 403 return deliveries.stream().map(delivery -> {
277 DeliveryRes deliveryRes = new DeliveryRes(); 404 DeliveryRes deliveryRes = new DeliveryRes();
......
1 +package com.apple.erp.service.impl;
2 +
3 +import com.apple.erp.entity.DealerInfo;
4 +import com.apple.erp.entity.OrderMain;
5 +import com.apple.erp.entity.Rebate;
6 +import com.apple.erp.mapper.DealerInfoMapper;
7 +import com.apple.erp.mapper.OrderMainMapper;
8 +import com.apple.erp.mapper.RebateMapper;
9 +import com.apple.erp.service.RebateSettlementService;
10 +import lombok.extern.slf4j.Slf4j;
11 +import org.springframework.beans.factory.annotation.Autowired;
12 +import org.springframework.stereotype.Service;
13 +
14 +import java.time.LocalDate;
15 +import java.time.LocalDateTime;
16 +import java.time.format.DateTimeFormatter;
17 +
18 +@Slf4j
19 +@Service
20 +public class RebateSettlementServiceImpl implements RebateSettlementService {
21 +
22 + @Autowired
23 + private OrderMainMapper orderMainMapper;
24 +
25 + @Autowired
26 + private DealerInfoMapper dealerInfoMapper;
27 +
28 + @Autowired
29 + private RebateMapper rebateMapper;
30 +
31 + @Override
32 + public void settleIfReady(String orderNo, String dealerCode, String dealerName) {
33 + try {
34 + OrderMain order = orderMainMapper.selectByOrderNo(orderNo);
35 + if (order == null || "2".equals(order.getDelFlag())) {
36 + return;
37 + }
38 + Integer deliveryStatus = order.getDeliveryStatus();
39 + Integer invoiceStatus = order.getInvoiceStatus();
40 + if (deliveryStatus == null || invoiceStatus == null || deliveryStatus != 1 || invoiceStatus != 1) {
41 + return;
42 + }
43 + if (order.getRebateCalcFlag() != null && order.getRebateCalcFlag() == 1) {
44 + return; // 已计算
45 + }
46 +
47 + java.math.BigDecimal rebateAmount = order.getRebateAmount() == null ? java.math.BigDecimal.ZERO : order.getRebateAmount();
48 +
49 + // 更新经销商待返利金额(扣减)
50 + DealerInfo dealer = dealerInfoMapper.selectOne(
51 + com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery(DealerInfo.class)
52 + .eq(DealerInfo::getDealerCode, dealerCode)
53 + .eq(DealerInfo::getDelFlag, "0")
54 + .last("LIMIT 1")
55 + );
56 + if (dealer != null) {
57 + java.math.BigDecimal pending = dealer.getPendingRebateAmount() == null ? java.math.BigDecimal.ZERO : dealer.getPendingRebateAmount();
58 + java.math.BigDecimal used = dealer.getUsedRebateAmount() == null ? java.math.BigDecimal.ZERO : dealer.getUsedRebateAmount();
59 + dealer.setPendingRebateAmount(pending.subtract(rebateAmount));
60 + dealer.setUsedRebateAmount(used.add(rebateAmount));
61 + dealer.setLastRebateUpdateTime(LocalDateTime.now());
62 + dealer.setUpdateTime(LocalDateTime.now());
63 + dealerInfoMapper.updateById(dealer);
64 + }
65 +
66 + // 生成返利台账(扣减)
67 + Rebate ledger = new Rebate();
68 + LocalDate currentDate = LocalDate.now();
69 + String[] rebateInfo = generateRebateNo(currentDate);
70 + String rebateNo = rebateInfo[0];
71 + Integer tIndex = Integer.valueOf(rebateInfo[1]);
72 +
73 + ledger.setRebateNo(rebateNo);
74 + ledger.setOrderNo(orderNo);
75 + ledger.setDealerCode(dealerCode);
76 + ledger.setDealerName(dealerName);
77 + ledger.setProductCode("ALL");
78 + ledger.setRebateAmount(rebateAmount);
79 + ledger.setRebateDate(currentDate);
80 + ledger.setOperateType(2); // 扣减
81 + ledger.setCalcFlag(1);
82 + ledger.setTIndex(tIndex);
83 + ledger.setUploadTime(LocalDateTime.now());
84 + rebateMapper.insert(ledger);
85 +
86 + // 标记订单已计算
87 + order.setRebateCalcFlag(1);
88 + order.setUpdateTime(LocalDateTime.now());
89 + orderMainMapper.updateById(order);
90 + } catch (Exception e) {
91 + log.warn("结转返利台账失败: orderNo={}, err={}", orderNo, e.getMessage());
92 + }
93 + }
94 +
95 + /**
96 + * 生成返利编号
97 + * 格式:RB-YYYYMMDD-00001
98 + *
99 + * @param rebateDate 返利日期
100 + * @return 返利编号和序列号
101 + */
102 + private String[] generateRebateNo(LocalDate rebateDate) {
103 + String dateStr = rebateDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
104 +
105 + // 获取当天的最大序列号
106 + Integer maxIndex = rebateMapper.getMaxIndexByDate(rebateDate.toString());
107 + if (maxIndex == null) {
108 + maxIndex = 0;
109 + }
110 +
111 + // 生成下一个序列号
112 + int nextIndex = maxIndex + 1;
113 + String sequence = String.format("%05d", nextIndex);
114 +
115 + String rebateNo = "RB-" + dateStr + "-" + sequence;
116 + return new String[]{rebateNo, String.valueOf(nextIndex)};
117 + }
118 +}
119 +
120 +
1 +package com.apple.erp.util;
2 +
3 +import com.apple.erp.entity.OrderMain;
4 +import com.apple.erp.entity.ExceptionWorkorder;
5 +import com.apple.erp.mapper.OrderMainMapper;
6 +import com.apple.erp.mapper.ExceptionWorkorderMapper;
7 +import org.springframework.beans.factory.annotation.Autowired;
8 +import org.springframework.stereotype.Component;
9 +import org.springframework.transaction.annotation.Transactional;
10 +
11 +import java.time.LocalDate;
12 +import java.time.LocalDateTime;
13 +import java.time.format.DateTimeFormatter;
14 +
15 +/**
16 + * 时序逻辑验证工具类
17 + * 用于验证订单、出库、开票的时间逻辑关系
18 + */
19 +@Component
20 +public class TimingValidationUtil {
21 +
22 + @Autowired
23 + private OrderMainMapper orderMainMapper;
24 +
25 + @Autowired
26 + private ExceptionWorkorderMapper exceptionWorkorderMapper;
27 +
28 + private static final String LOGIC_VALIDATION_EXCEPTION = "逻辑验证异常";
29 + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
30 + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
31 +
32 + /**
33 + * 验证出库时间逻辑
34 + * 规则:出库时间不得早于订单创建时间
35 + *
36 + * @param orderNo 订单号
37 + * @param deliveryTime 出库时间
38 + * @return 验证结果
39 + */
40 + public ValidationResult validateDeliveryTiming(String orderNo, LocalDateTime deliveryTime) {
41 + ValidationResult result = new ValidationResult();
42 +
43 + try {
44 + // 查询订单信息
45 + OrderMain order = orderMainMapper.selectByOrderNo(orderNo);
46 + if (order == null) {
47 + result.setValid(false);
48 + result.setErrorMessage("订单不存在");
49 + return result;
50 + }
51 +
52 + // 验证出库时间不得早于订单创建时间
53 + if (deliveryTime.isBefore(order.getOrderDate())) {
54 + result.setValid(false);
55 + result.setErrorMessage("出库时间不得早于订单创建时间");
56 + result.setExceptionType(LOGIC_VALIDATION_EXCEPTION);
57 + result.setExceptionDescription(String.format(
58 + "订单[%s]出库时间[%s]早于订单创建时间[%s],违反时序逻辑",
59 + orderNo,
60 + deliveryTime.format(DATE_TIME_FORMATTER),
61 + order.getCreateTime().format(DATE_TIME_FORMATTER)
62 + ));
63 + } else {
64 + result.setValid(true);
65 + }
66 +
67 + } catch (Exception e) {
68 + result.setValid(false);
69 + result.setErrorMessage("验证过程中发生异常: " + e.getMessage());
70 + }
71 +
72 + return result;
73 + }
74 +
75 + /**
76 + * 验证开票时间逻辑
77 + * 规则:开票时间不得早于出库时间
78 + *
79 + * @param orderNo 订单号
80 + * @param invoiceTime 开票时间
81 + * @return 验证结果
82 + */
83 + public ValidationResult validateInvoiceTiming(String orderNo, LocalDateTime invoiceTime) {
84 + ValidationResult result = new ValidationResult();
85 +
86 + try {
87 + // 查询订单信息
88 + OrderMain order = orderMainMapper.selectByOrderNo(orderNo);
89 + if (order == null) {
90 + result.setValid(false);
91 + result.setErrorMessage("订单不存在");
92 + return result;
93 + }
94 +
95 + // 查询该订单的最新出库时间
96 + LocalDateTime latestDeliveryTime = orderMainMapper.getLatestDeliveryTimeByOrderNo(orderNo);
97 +
98 + if (latestDeliveryTime == null) {
99 + result.setValid(false);
100 + result.setErrorMessage("该订单尚未出库,无法开票");
101 + result.setExceptionType(LOGIC_VALIDATION_EXCEPTION);
102 + result.setExceptionDescription(String.format(
103 + "订单[%s]尚未出库,开票时间[%s]违反时序逻辑",
104 + orderNo,
105 + invoiceTime.format(DATE_TIME_FORMATTER)
106 + ));
107 + } else if (invoiceTime.isBefore(latestDeliveryTime)) {
108 + result.setValid(false);
109 + result.setErrorMessage("开票时间不得早于出库时间");
110 + result.setExceptionType(LOGIC_VALIDATION_EXCEPTION);
111 + result.setExceptionDescription(String.format(
112 + "订单[%s]开票时间[%s]早于出库时间[%s],违反时序逻辑",
113 + orderNo,
114 + invoiceTime.format(DATE_TIME_FORMATTER),
115 + latestDeliveryTime.format(DATE_TIME_FORMATTER)
116 + ));
117 + } else {
118 + result.setValid(true);
119 + }
120 +
121 + } catch (Exception e) {
122 + result.setValid(false);
123 + result.setErrorMessage("验证过程中发生异常: " + e.getMessage());
124 + }
125 +
126 + return result;
127 + }
128 +
129 + /**
130 + * 生成异常工单号
131 + * 格式:WO-YYYYMMDD-00001
132 + *
133 + * @return 工单号
134 + */
135 + public String generateWorkorderNo() {
136 + LocalDate today = LocalDate.now();
137 + String dateStr = today.format(DATE_FORMATTER);
138 +
139 + // 获取当日最大序列号
140 + Integer maxIndex = exceptionWorkorderMapper.getMaxIndexByDate(today);
141 + int nextIndex = maxIndex + 1;
142 +
143 + // 格式化为5位数字,不足补零
144 + String indexStr = String.format("%05d", nextIndex);
145 +
146 + return String.format("WO-%s-%s", dateStr, indexStr);
147 + }
148 +
149 + /**
150 + * 创建异常工单
151 + *
152 + * @param orderNo 订单号
153 + * @param exceptionType 异常类型
154 + * @param exceptionDescription 异常描述
155 + * @param relatedEntityType 关联实体类型(DELIVERY/INVOICE)
156 + * @param relatedEntityId 关联实体ID
157 + * @return 异常工单ID
158 + */
159 + public Long createExceptionWorkorder(String orderNo, String dealerCode,String dealerName,String exceptionType,
160 + String exceptionDescription,
161 + String relatedEntityType, Long relatedEntityId) {
162 + try {
163 + // 生成工单号
164 + String workorderNo = generateWorkorderNo();
165 +
166 + // 获取当日序列号
167 + LocalDate today = LocalDate.now();
168 + Integer maxIndex = exceptionWorkorderMapper.getMaxIndexByDate(today);
169 + int currentIndex = maxIndex + 1;
170 +
171 + ExceptionWorkorder workorder = new ExceptionWorkorder();
172 + workorder.setWorkorderNo(workorderNo);
173 + workorder.setIndex(currentIndex);
174 + workorder.setOrderNo(orderNo);
175 + workorder.setDealerCode(dealerCode);
176 + workorder.setDealerName(dealerName);
177 + workorder.setExceptionType(1); // 1-逻辑验证异常
178 + workorder.setExceptionDesc(exceptionDescription);
179 + workorder.setDataSource(relatedEntityType);
180 + workorder.setWorkorderStatus(1); // 1-待处理
181 + workorder.setSeverityLevel(2); // 2-中
182 + workorder.setCreateTime(LocalDateTime.now());
183 + workorder.setCreateBy("system");
184 +
185 + exceptionWorkorderMapper.insert(workorder);
186 + return workorder.getWorkorderId();
187 +
188 + } catch (Exception e) {
189 + throw new RuntimeException("创建异常工单失败: " + e.getMessage(), e);
190 + }
191 + }
192 +
193 + /**
194 + * 验证结果类
195 + */
196 + public static class ValidationResult {
197 + private boolean valid;
198 + private String errorMessage;
199 + private String exceptionType;
200 + private String exceptionDescription;
201 +
202 + public boolean isValid() {
203 + return valid;
204 + }
205 +
206 + public void setValid(boolean valid) {
207 + this.valid = valid;
208 + }
209 +
210 + public String getErrorMessage() {
211 + return errorMessage;
212 + }
213 +
214 + public void setErrorMessage(String errorMessage) {
215 + this.errorMessage = errorMessage;
216 + }
217 +
218 + public String getExceptionType() {
219 + return exceptionType;
220 + }
221 +
222 + public void setExceptionType(String exceptionType) {
223 + this.exceptionType = exceptionType;
224 + }
225 +
226 + public String getExceptionDescription() {
227 + return exceptionDescription;
228 + }
229 +
230 + public void setExceptionDescription(String exceptionDescription) {
231 + this.exceptionDescription = exceptionDescription;
232 + }
233 + }
234 +}
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
5 <!-- 返利明细基础字段 --> 5 <!-- 返利明细基础字段 -->
6 <sql id="baseColumns"> 6 <sql id="baseColumns">
7 rebate_id, rebate_no, order_no, dealer_code, dealer_name, product_code, 7 rebate_id, rebate_no, order_no, dealer_code, dealer_name, product_code,
8 - rebate_amount, rebate_date, operate_type, calc_flag, upload_time, 8 + rebate_amount, rebate_date, operate_type, calc_flag, t_index, upload_time,
9 create_by, create_time, update_by, update_time 9 create_by, create_time, update_by, update_time
10 </sql> 10 </sql>
11 11
...@@ -97,6 +97,14 @@ ...@@ -97,6 +97,14 @@
97 AND rebate_no = #{rebateNo} 97 AND rebate_no = #{rebateNo}
98 </select> 98 </select>
99 99
100 + <!-- 获取指定日期的最大序列号 -->
101 + <select id="getMaxIndexByDate" resultType="java.lang.Integer">
102 + SELECT COALESCE(MAX(t_index), 0)
103 + FROM t_rebate_detail
104 + WHERE del_flag = '0'
105 + AND rebate_date = #{rebateDate}
106 + </select>
107 +
100 <!-- 获取返利月度统计数据 --> 108 <!-- 获取返利月度统计数据 -->
101 <select id="getRebateMonthlyStats" resultType="java.util.Map"> 109 <select id="getRebateMonthlyStats" resultType="java.util.Map">
102 SELECT 110 SELECT
......
1 +package com.apple.erp.service.impl;
2 +
3 +import com.apple.erp.dto.DeliveryAddReq;
4 +import com.apple.erp.dto.DeliveryUpdateReq;
5 +import com.apple.erp.entity.OrderMain;
6 +import com.apple.erp.exception.DealerMismatchException;
7 +import com.apple.erp.mapper.DeliveryItemMapper;
8 +import com.apple.erp.mapper.OrderMainMapper;
9 +import com.apple.erp.service.RebateSettlementService;
10 +import com.apple.erp.util.TimingValidationUtil;
11 +import org.junit.jupiter.api.BeforeEach;
12 +import org.junit.jupiter.api.Test;
13 +import org.junit.jupiter.api.extension.ExtendWith;
14 +import org.mockito.InjectMocks;
15 +import org.mockito.Mock;
16 +import org.mockito.junit.jupiter.MockitoExtension;
17 +
18 +import static org.junit.jupiter.api.Assertions.*;
19 +import static org.mockito.Mockito.*;
20 +
21 +/**
22 + * DeliveryMainServiceImpl经销商验证测试类
23 + */
24 +@ExtendWith(MockitoExtension.class)
25 +class DeliveryMainServiceImplDealerValidationTest {
26 +
27 + @Mock
28 + private OrderMainMapper orderMainMapper;
29 +
30 + @Mock
31 + private DeliveryItemMapper deliveryItemMapper;
32 +
33 + @Mock
34 + private TimingValidationUtil timingValidationUtil;
35 +
36 + @Mock
37 + private RebateSettlementService rebateSettlementService;
38 +
39 + @InjectMocks
40 + private DeliveryMainServiceImpl deliveryMainService;
41 +
42 + private OrderMain testOrder;
43 + private DeliveryAddReq deliveryAddReq;
44 + private DeliveryUpdateReq deliveryUpdateReq;
45 +
46 + @BeforeEach
47 + void setUp() {
48 + // 创建测试订单
49 + testOrder = new OrderMain();
50 + testOrder.setOrderId(1L);
51 + testOrder.setOrderNo("ORDER001");
52 + testOrder.setDealerCode("DEALER001");
53 + testOrder.setDealerName("测试经销商");
54 +
55 + // 创建测试出库单请求
56 + deliveryAddReq = new DeliveryAddReq();
57 + deliveryAddReq.setDeliveryNo("DELIVERY001");
58 + deliveryAddReq.setOrderNo("ORDER001");
59 + deliveryAddReq.setDealerCode("DEALER001");
60 + deliveryAddReq.setDealerName("测试经销商");
61 +
62 + // 创建测试出库单更新请求
63 + deliveryUpdateReq = new DeliveryUpdateReq();
64 + deliveryUpdateReq.setDeliveryId(1L);
65 + deliveryUpdateReq.setDeliveryNo("DELIVERY001");
66 + deliveryUpdateReq.setOrderNo("ORDER001");
67 + deliveryUpdateReq.setDealerCode("DEALER001");
68 + deliveryUpdateReq.setDealerName("测试经销商");
69 + }
70 +
71 + @Test
72 + void testAddDelivery_DealerConsistency_Success() {
73 + // 模拟订单存在且经销商一致
74 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(testOrder);
75 + when(orderMainMapper.selectCount(any())).thenReturn(1L); // 订单存在
76 + when(deliveryItemMapper.insert(any())).thenReturn(1);
77 +
78 + // 这里需要模拟更多的依赖,实际测试中需要更完整的mock设置
79 + // 由于方法比较复杂,这里主要验证验证逻辑的调用
80 +
81 + // 验证方法会调用selectByOrderNo来获取订单信息
82 + verify(orderMainMapper, atLeastOnce()).selectByOrderNo("ORDER001");
83 + }
84 +
85 + @Test
86 + void testAddDelivery_DealerMismatch_ThrowsException() {
87 + // 设置出库单中的经销商与订单中的经销商不一致
88 + deliveryAddReq.setDealerCode("DEALER002"); // 不同的经销商编码
89 +
90 + // 模拟订单存在
91 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(testOrder);
92 + when(orderMainMapper.selectCount(any())).thenReturn(1L);
93 +
94 + // 验证会抛出DealerMismatchException异常
95 + assertThrows(DealerMismatchException.class, () -> {
96 + deliveryMainService.addDelivery(deliveryAddReq);
97 + });
98 + }
99 +
100 + @Test
101 + void testUpdateDelivery_DealerConsistency_Success() {
102 + // 模拟订单存在且经销商一致
103 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(testOrder);
104 + when(orderMainMapper.selectCount(any())).thenReturn(1L);
105 +
106 + // 验证方法会调用selectByOrderNo来获取订单信息
107 + verify(orderMainMapper, atLeastOnce()).selectByOrderNo("ORDER001");
108 + }
109 +
110 + @Test
111 + void testUpdateDelivery_DealerMismatch_ThrowsException() {
112 + // 设置出库单中的经销商与订单中的经销商不一致
113 + deliveryUpdateReq.setDealerCode("DEALER002"); // 不同的经销商编码
114 +
115 + // 模拟订单存在
116 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(testOrder);
117 + when(orderMainMapper.selectCount(any())).thenReturn(1L);
118 +
119 + // 验证会抛出DealerMismatchException异常
120 + assertThrows(DealerMismatchException.class, () -> {
121 + deliveryMainService.updateDelivery(deliveryUpdateReq);
122 + });
123 + }
124 +
125 + @Test
126 + void testValidateDealerConsistency_OrderNotFound_ThrowsException() {
127 + // 模拟订单不存在
128 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(null);
129 +
130 + // 验证会抛出IllegalArgumentException异常
131 + assertThrows(IllegalArgumentException.class, () -> {
132 + deliveryMainService.addDelivery(deliveryAddReq);
133 + });
134 + }
135 +
136 + @Test
137 + void testDealerMismatchException_MessageFormat() {
138 + // 测试异常消息格式
139 + String expectedMessage = "出库单中的经销商编码[DEALER002]与关联订单[ORDER001]中的经销商编码[DEALER001]不一致";
140 +
141 + DealerMismatchException exception = new DealerMismatchException(expectedMessage);
142 + assertEquals(expectedMessage, exception.getMessage());
143 + }
144 +}
1 +package com.apple.erp.service.impl;
2 +
3 +import com.apple.erp.dto.InvoiceAddReq;
4 +import com.apple.erp.dto.InvoiceUpdateReq;
5 +import com.apple.erp.entity.OrderMain;
6 +import com.apple.erp.entity.DeliveryMain;
7 +import com.apple.erp.exception.InvoiceValidationException;
8 +import com.apple.erp.mapper.DeliveryMainMapper;
9 +import com.apple.erp.mapper.InvoiceItemMapper;
10 +import com.apple.erp.mapper.OrderMainMapper;
11 +import com.apple.erp.service.RebateSettlementService;
12 +import com.apple.erp.util.TimingValidationUtil;
13 +import org.junit.jupiter.api.BeforeEach;
14 +import org.junit.jupiter.api.Test;
15 +import org.junit.jupiter.api.extension.ExtendWith;
16 +import org.mockito.InjectMocks;
17 +import org.mockito.Mock;
18 +import org.mockito.junit.jupiter.MockitoExtension;
19 +
20 +import java.math.BigDecimal;
21 +import java.time.LocalDate;
22 +import java.util.ArrayList;
23 +
24 +import static org.junit.jupiter.api.Assertions.*;
25 +import static org.mockito.Mockito.*;
26 +
27 +/**
28 + * InvoiceMainServiceImpl验证测试类
29 + */
30 +@ExtendWith(MockitoExtension.class)
31 +class InvoiceMainServiceImplValidationTest {
32 +
33 + @Mock
34 + private OrderMainMapper orderMainMapper;
35 +
36 + @Mock
37 + private DeliveryMainMapper deliveryMainMapper;
38 +
39 + @Mock
40 + private InvoiceItemMapper invoiceItemMapper;
41 +
42 + @Mock
43 + private TimingValidationUtil timingValidationUtil;
44 +
45 + @Mock
46 + private RebateSettlementService rebateSettlementService;
47 +
48 + @InjectMocks
49 + private InvoiceMainServiceImpl invoiceMainService;
50 +
51 + private OrderMain testOrder;
52 + private DeliveryMain testDelivery;
53 + private InvoiceAddReq invoiceAddReq;
54 + private InvoiceUpdateReq invoiceUpdateReq;
55 +
56 + @BeforeEach
57 + void setUp() {
58 + // 创建测试订单
59 + testOrder = new OrderMain();
60 + testOrder.setOrderId(1L);
61 + testOrder.setOrderNo("ORDER001");
62 + testOrder.setDealerCode("DEALER001");
63 + testOrder.setDealerName("测试经销商");
64 +
65 + // 创建测试出库单
66 + testDelivery = new DeliveryMain();
67 + testDelivery.setDeliveryId(1L);
68 + testDelivery.setDeliveryNo("DELIVERY001");
69 + testDelivery.setDealerCode("DEALER001");
70 + testDelivery.setDealerName("测试经销商");
71 + testDelivery.setOrderNo("ORDER001");
72 +
73 + // 创建测试发票请求
74 + invoiceAddReq = new InvoiceAddReq();
75 + invoiceAddReq.setInvoiceNo("INVOICE001");
76 + invoiceAddReq.setOrderNo("ORDER001");
77 + invoiceAddReq.setDeliveryNo("DELIVERY001");
78 + invoiceAddReq.setDealerCode("DEALER001");
79 + invoiceAddReq.setDealerName("测试经销商");
80 + invoiceAddReq.setTotalAmount(new BigDecimal("1000.00"));
81 + invoiceAddReq.setInvoiceDate(LocalDate.now());
82 + invoiceAddReq.setInvoiceItems(new ArrayList<>());
83 +
84 + // 创建测试发票更新请求
85 + invoiceUpdateReq = new InvoiceUpdateReq();
86 + invoiceUpdateReq.setInvoiceId(1L);
87 + invoiceUpdateReq.setInvoiceNo("INVOICE001");
88 + invoiceUpdateReq.setOrderNo("ORDER001");
89 + invoiceUpdateReq.setDeliveryNo("DELIVERY001");
90 + invoiceUpdateReq.setDealerCode("DEALER001");
91 + invoiceUpdateReq.setDealerName("测试经销商");
92 + invoiceUpdateReq.setTotalAmount(new BigDecimal("1000.00"));
93 + invoiceUpdateReq.setInvoiceDate(LocalDate.now());
94 + invoiceUpdateReq.setInvoiceItems(new ArrayList<>());
95 + }
96 +
97 + @Test
98 + void testAddInvoice_OrderDealerMismatch_ThrowsException() {
99 + // 设置发票中的经销商与订单中的经销商不一致
100 + invoiceAddReq.setDealerCode("DEALER002"); // 不同的经销商编码
101 +
102 + // 模拟订单存在
103 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(testOrder);
104 +
105 + // 验证会抛出InvoiceValidationException异常
106 + assertThrows(InvoiceValidationException.class, () -> {
107 + invoiceMainService.addInvoice(invoiceAddReq);
108 + });
109 + }
110 +
111 + @Test
112 + void testAddInvoice_DeliveryDealerMismatch_ThrowsException() {
113 + // 设置发票中的经销商与出库单中的经销商不一致
114 + invoiceAddReq.setDealerCode("DEALER002"); // 不同的经销商编码
115 +
116 + // 模拟订单和出库单存在
117 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(testOrder);
118 + when(deliveryMainMapper.selectByDeliveryNo("DELIVERY001")).thenReturn(testDelivery);
119 +
120 + // 验证会抛出InvoiceValidationException异常
121 + assertThrows(InvoiceValidationException.class, () -> {
122 + invoiceMainService.addInvoice(invoiceAddReq);
123 + });
124 + }
125 +
126 + @Test
127 + void testAddInvoice_OrderNotFound_ThrowsException() {
128 + // 模拟订单不存在
129 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(null);
130 +
131 + // 验证会抛出IllegalArgumentException异常
132 + assertThrows(IllegalArgumentException.class, () -> {
133 + invoiceMainService.addInvoice(invoiceAddReq);
134 + });
135 + }
136 +
137 + @Test
138 + void testAddInvoice_DeliveryNotFound_ThrowsException() {
139 + // 模拟订单存在但出库单不存在
140 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(testOrder);
141 + when(deliveryMainMapper.selectByDeliveryNo("DELIVERY001")).thenReturn(null);
142 +
143 + // 验证会抛出IllegalArgumentException异常
144 + assertThrows(IllegalArgumentException.class, () -> {
145 + invoiceMainService.addInvoice(invoiceAddReq);
146 + });
147 + }
148 +
149 + @Test
150 + void testUpdateInvoice_OrderDealerMismatch_ThrowsException() {
151 + // 设置发票中的经销商与订单中的经销商不一致
152 + invoiceUpdateReq.setDealerCode("DEALER002"); // 不同的经销商编码
153 +
154 + // 模拟订单存在
155 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(testOrder);
156 +
157 + // 验证会抛出InvoiceValidationException异常
158 + assertThrows(InvoiceValidationException.class, () -> {
159 + invoiceMainService.updateInvoice(invoiceUpdateReq);
160 + });
161 + }
162 +
163 + @Test
164 + void testUpdateInvoice_DeliveryDealerMismatch_ThrowsException() {
165 + // 设置发票中的经销商与出库单中的经销商不一致
166 + invoiceUpdateReq.setDealerCode("DEALER002"); // 不同的经销商编码
167 +
168 + // 模拟订单和出库单存在
169 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(testOrder);
170 + when(deliveryMainMapper.selectByDeliveryNo("DELIVERY001")).thenReturn(testDelivery);
171 +
172 + // 验证会抛出InvoiceValidationException异常
173 + assertThrows(InvoiceValidationException.class, () -> {
174 + invoiceMainService.updateInvoice(invoiceUpdateReq);
175 + });
176 + }
177 +
178 + @Test
179 + void testAddInvoice_WithoutDeliveryNo_Success() {
180 + // 不提供出库单号的情况
181 + invoiceAddReq.setDeliveryNo(null);
182 +
183 + // 模拟订单存在
184 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(testOrder);
185 +
186 + // 验证方法会调用selectByOrderNo来获取订单信息
187 + verify(orderMainMapper, atLeastOnce()).selectByOrderNo("ORDER001");
188 + // 验证不会调用出库单查询
189 + verify(deliveryMainMapper, never()).selectByDeliveryNo(anyString());
190 + }
191 +
192 + @Test
193 + void testInvoiceValidationException_MessageFormat() {
194 + // 测试异常消息格式
195 + String expectedMessage = "发票中的经销商编码[DEALER002]与关联订单[ORDER001]中的经销商编码[DEALER001]不一致";
196 +
197 + InvoiceValidationException exception = new InvoiceValidationException(expectedMessage);
198 + assertEquals(expectedMessage, exception.getMessage());
199 + }
200 +
201 + @Test
202 + void testValidateOrderConsistency_Success() {
203 + // 测试订单经销商一致性验证成功的情况
204 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(testOrder);
205 +
206 + // 这里我们通过addInvoice方法来间接测试验证逻辑
207 + // 由于方法比较复杂,这里主要验证验证逻辑的调用
208 +
209 + // 验证方法会调用selectByOrderNo来获取订单信息
210 + verify(orderMainMapper, atLeastOnce()).selectByOrderNo("ORDER001");
211 + }
212 +
213 + @Test
214 + void testValidateDeliveryConsistency_Success() {
215 + // 测试出库单经销商一致性验证成功的情况
216 + when(orderMainMapper.selectByOrderNo("ORDER001")).thenReturn(testOrder);
217 + when(deliveryMainMapper.selectByDeliveryNo("DELIVERY001")).thenReturn(testDelivery);
218 +
219 + // 这里我们通过addInvoice方法来间接测试验证逻辑
220 + // 由于方法比较复杂,这里主要验证验证逻辑的调用
221 +
222 + // 验证方法会调用selectByDeliveryNo来获取出库单信息
223 + verify(deliveryMainMapper, atLeastOnce()).selectByDeliveryNo("DELIVERY001");
224 + }
225 +}
1 +package com.apple.erp.service.impl;
2 +
3 +import com.apple.erp.mapper.RebateMapper;
4 +import org.junit.jupiter.api.BeforeEach;
5 +import org.junit.jupiter.api.Test;
6 +import org.junit.jupiter.api.extension.ExtendWith;
7 +import org.mockito.InjectMocks;
8 +import org.mockito.Mock;
9 +import org.mockito.junit.jupiter.MockitoExtension;
10 +
11 +import java.time.LocalDate;
12 +import java.time.format.DateTimeFormatter;
13 +
14 +import static org.junit.jupiter.api.Assertions.*;
15 +import static org.mockito.Mockito.*;
16 +
17 +/**
18 + * RebateSettlementServiceImpl测试类
19 + */
20 +@ExtendWith(MockitoExtension.class)
21 +class RebateSettlementServiceImplTest {
22 +
23 + @Mock
24 + private RebateMapper rebateMapper;
25 +
26 + @InjectMocks
27 + private RebateSettlementServiceImpl rebateSettlementService;
28 +
29 + @BeforeEach
30 + void setUp() {
31 + // 使用反射来测试私有方法
32 + // 这里我们主要测试公开的方法
33 + }
34 +
35 + @Test
36 + void testGenerateRebateNo_FirstRecordOfDay() {
37 + // 模拟当天没有记录的情况
38 + when(rebateMapper.getMaxIndexByDate(anyString())).thenReturn(null);
39 +
40 + // 使用反射调用私有方法进行测试
41 + // 这里我们通过settleIfReady方法来间接测试
42 + // 实际测试中应该通过公开的方法来验证结果
43 +
44 + LocalDate testDate = LocalDate.of(2024, 1, 15);
45 + String expectedDateStr = testDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
46 + String expectedRebateNo = "RB-" + expectedDateStr + "-00001";
47 +
48 + // 验证生成的RebateNo格式
49 + assertTrue(expectedRebateNo.matches("RB-\\d{8}-\\d{5}"));
50 + assertEquals("RB-20240115-00001", expectedRebateNo);
51 + }
52 +
53 + @Test
54 + void testGenerateRebateNo_SubsequentRecords() {
55 + // 模拟当天已有记录的情况
56 + when(rebateMapper.getMaxIndexByDate(anyString())).thenReturn(5);
57 +
58 + LocalDate testDate = LocalDate.of(2024, 1, 15);
59 + String expectedDateStr = testDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
60 + String expectedRebateNo = "RB-" + expectedDateStr + "-00006";
61 +
62 + // 验证生成的RebateNo格式
63 + assertTrue(expectedRebateNo.matches("RB-\\d{8}-\\d{5}"));
64 + assertEquals("RB-20240115-00006", expectedRebateNo);
65 + }
66 +
67 + @Test
68 + void testRebateNoFormat() {
69 + // 测试RebateNo格式:RB-YYYYMMDD-00001
70 + String pattern = "RB-\\d{8}-\\d{5}";
71 +
72 + // 测试有效格式
73 + assertTrue("RB-20240115-00001".matches(pattern));
74 + assertTrue("RB-20241231-99999".matches(pattern));
75 +
76 + // 测试无效格式
77 + assertFalse("RB-20240115-0001".matches(pattern)); // 序列号应该是5位
78 + assertFalse("RB-2024015-00001".matches(pattern)); // 日期应该是8位
79 + assertFalse("RB20240115-00001".matches(pattern)); // 缺少连字符
80 + }
81 +}
1 +package com.apple.erp.util;
2 +
3 +import org.junit.jupiter.api.Test;
4 +import org.springframework.boot.test.context.SpringBootTest;
5 +import org.springframework.test.context.ActiveProfiles;
6 +
7 +import static org.junit.jupiter.api.Assertions.*;
8 +
9 +/**
10 + * TimingValidationUtil 测试类
11 + *
12 + * @author Apple ERP System
13 + * @since 2025-01-09
14 + */
15 +@SpringBootTest
16 +@ActiveProfiles("test")
17 +public class TimingValidationUtilTest {
18 +
19 + @Test
20 + public void testGenerateWorkorderNo() {
21 + // 注意:这个测试需要数据库连接,在实际环境中运行
22 + // 这里只是展示测试结构
23 +
24 + // 测试工单号格式
25 + String workorderNo = "WO-20250109-00001";
26 +
27 + // 验证工单号格式
28 + assertTrue(workorderNo.startsWith("WO-"));
29 + assertTrue(workorderNo.matches("WO-\\d{8}-\\d{5}"));
30 +
31 + // 验证日期部分
32 + String datePart = workorderNo.substring(3, 11);
33 + assertEquals("20250109", datePart);
34 +
35 + // 验证序列号部分
36 + String indexPart = workorderNo.substring(12);
37 + assertEquals("00001", indexPart);
38 + }
39 +
40 + @Test
41 + public void testWorkorderNoFormat() {
42 + // 测试工单号格式验证
43 + String[] validWorkorderNos = {
44 + "WO-20250109-00001",
45 + "WO-20250109-00002",
46 + "WO-20250109-99999"
47 + };
48 +
49 + for (String workorderNo : validWorkorderNos) {
50 + assertTrue(workorderNo.matches("WO-\\d{8}-\\d{5}"),
51 + "工单号格式不正确: " + workorderNo);
52 + }
53 +
54 + String[] invalidWorkorderNos = {
55 + "WO-20250109-1", // 序列号不足5位
56 + "WO-2025019-00001", // 日期不足8位
57 + "WO-20250109-0001", // 序列号不足5位
58 + "WO-20250109-000001" // 序列号超过5位
59 + };
60 +
61 + for (String workorderNo : invalidWorkorderNos) {
62 + assertFalse(workorderNo.matches("WO-\\d{8}-\\d{5}"),
63 + "工单号格式应该无效: " + workorderNo);
64 + }
65 + }
66 +}
...@@ -143,6 +143,7 @@ CREATE TABLE t_rebate_detail ( ...@@ -143,6 +143,7 @@ CREATE TABLE t_rebate_detail (
143 rebate_date DATE NOT NULL COMMENT '返利记录日期', 143 rebate_date DATE NOT NULL COMMENT '返利记录日期',
144 operate_type TINYINT NOT NULL COMMENT '操作类型(1-新增/2-扣减)', 144 operate_type TINYINT NOT NULL COMMENT '操作类型(1-新增/2-扣减)',
145 calc_flag TINYINT DEFAULT 0 COMMENT '返利计算状态(0-未计算/1-已计算)', 145 calc_flag TINYINT DEFAULT 0 COMMENT '返利计算状态(0-未计算/1-已计算)',
146 + t_index INT DEFAULT 0 COMMENT '序列号索引(用于生成RebateNo)',
146 upload_time DATETIME COMMENT '数据上传时间', 147 upload_time DATETIME COMMENT '数据上传时间',
147 create_by VARCHAR(64) DEFAULT '' COMMENT '创建者', 148 create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
148 create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 149 create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
......
1 +-- =============================================
2 +-- 添加t_index字段到t_rebate_detail表
3 +-- =============================================
4 +
5 +-- 添加t_index字段
6 +ALTER TABLE t_rebate_detail
7 +ADD COLUMN t_index INT DEFAULT 0 COMMENT '序列号索引(用于生成RebateNo)'
8 +AFTER calc_flag;
9 +
10 +-- 为现有数据初始化t_index值
11 +-- 按日期分组,为每条记录分配递增的序列号
12 +UPDATE t_rebate_detail t1
13 +SET t_index = (
14 + SELECT COUNT(*)
15 + FROM t_rebate_detail t2
16 + WHERE t2.rebate_date = t1.rebate_date
17 + AND t2.rebate_id <= t1.rebate_id
18 + AND t2.del_flag = '0'
19 +)
20 +WHERE t1.del_flag = '0';
21 +
22 +-- 添加索引以提高查询性能
23 +CREATE INDEX idx_rebate_t_index ON t_rebate_detail(t_index);
24 +CREATE INDEX idx_rebate_date_t_index ON t_rebate_detail(rebate_date, t_index);
1 +-- Apple经销商ERP系统 - 修复发票管理权限配置脚本
2 +-- 修复发票详情权限不匹配问题
3 +-- 创建时间: 2025-01-27
4 +-- 版本: v1.0
5 +
6 +-- 设置数据库和字符集
7 +SET NAMES utf8mb4;
8 +SET FOREIGN_KEY_CHECKS = 0;
9 +
10 +-- 使用数据库
11 +USE apple_erp;
12 +
13 +-- =========================================
14 +-- 1. 修复发票详情权限配置
15 +-- =========================================
16 +
17 +-- 更新发票详情权限从 invoice:query 改为 invoice:detail
18 +UPDATE t_sys_menu
19 +SET perms = 'invoice:detail'
20 +WHERE menu_id = 405
21 +AND perms = 'invoice:query';
22 +
23 +-- 更新发票删除权限从 invoice:remove 改为 invoice:delete
24 +UPDATE t_sys_menu
25 +SET perms = 'invoice:delete'
26 +WHERE menu_id = 404
27 +AND perms = 'invoice:remove';
28 +
29 +-- 输出修复结果
30 +SELECT '发票管理权限配置修复完成!' AS message;
31 +
32 +-- 重置外键检查
33 +SET FOREIGN_KEY_CHECKS = 1;
1 +-- 为异常工单表添加index字段用于生成序列号
2 +-- 执行时间:2025-01-09
3 +-- 说明:添加index字段用于生成工单号序列号,每天从1开始计数
4 +
5 +-- 1. 添加index字段
6 +ALTER TABLE t_exception_workorder
7 +ADD COLUMN `index` INT DEFAULT 0 COMMENT '当日序列号,用于生成工单号'
8 +AFTER workorder_no;
9 +
10 +-- 2. 为index字段添加索引,提高查询性能
11 +CREATE INDEX idx_workorder_index ON t_exception_workorder(`index`);
12 +
13 +-- 3. 为create_time和index字段创建复合索引,用于按日期查询最大序列号
14 +CREATE INDEX idx_workorder_date_index ON t_exception_workorder(DATE(create_time), `index`);
15 +
16 +-- 4. 更新现有数据的index字段(可选,如果需要为现有数据生成序列号)
17 +-- 注意:这里只是示例,实际执行时需要根据业务需求决定是否更新现有数据
18 +-- UPDATE t_exception_workorder SET `index` = 0 WHERE `index` IS NULL;
1 +# 出库单经销商验证功能更新
2 +
3 +## 修改概述
4 +
5 +`DeliveryMainServiceImpl` 中的 `addDelivery``updateDelivery` 方法中添加了经销商一致性验证逻辑,确保出库单中的经销商与关联订单中的经销商保持一致。
6 +
7 +## 修改内容
8 +
9 +### 1. 新增自定义异常类
10 +
11 +创建了 `DealerMismatchException` 异常类,用于处理经销商不匹配的情况:
12 +
13 +```java
14 +public class DealerMismatchException extends RuntimeException {
15 + public DealerMismatchException(String message) {
16 + super(message);
17 + }
18 +
19 + public DealerMismatchException(String message, Throwable cause) {
20 + super(message, cause);
21 + }
22 +}
23 +```
24 +
25 +### 2. 新增验证方法
26 +
27 +`DeliveryMainServiceImpl` 中添加了 `validateDealerConsistency` 私有方法:
28 +
29 +```java
30 +private void validateDealerConsistency(String orderNo, String deliveryDealerCode) {
31 + // 根据订单编号查询订单信息
32 + OrderMain order = orderMainMapper.selectByOrderNo(orderNo);
33 + if (order == null) {
34 + throw new IllegalArgumentException("关联的订单不存在");
35 + }
36 +
37 + // 比较经销商编码
38 + if (!deliveryDealerCode.equals(order.getDealerCode())) {
39 + String errorMessage = String.format(
40 + "出库单中的经销商编码[%s]与关联订单[%s]中的经销商编码[%s]不一致",
41 + deliveryDealerCode, orderNo, order.getDealerCode()
42 + );
43 + throw new DealerMismatchException(errorMessage);
44 + }
45 +}
46 +```
47 +
48 +### 3. 修改addDelivery方法
49 +
50 +`addDelivery` 方法中添加了经销商验证逻辑:
51 +
52 +```java
53 +// 验证出库单中的经销商与关联订单中的经销商是否一致
54 +validateDealerConsistency(addReq.getOrderNo(), addReq.getDealerCode());
55 +```
56 +
57 +### 4. 修改updateDelivery方法
58 +
59 +`updateDelivery` 方法中添加了经销商验证逻辑:
60 +
61 +```java
62 +// 验证出库单中的经销商与关联订单中的经销商是否一致
63 +validateDealerConsistency(updateReq.getOrderNo(), updateReq.getDealerCode());
64 +```
65 +
66 +## 验证逻辑说明
67 +
68 +### 验证流程
69 +
70 +1. **订单存在性验证**:首先验证关联的订单编号是否在订单管理中存在
71 +2. **经销商一致性验证**:验证出库单中的经销商编码与关联订单中的经销商编码是否一致
72 +3. **异常处理**:如果经销商不一致,抛出 `DealerMismatchException` 异常
73 +
74 +### 验证时机
75 +
76 +- **新增出库单时**:在 `addDelivery` 方法中,在订单存在性验证之后立即进行经销商验证
77 +- **修改出库单时**:在 `updateDelivery` 方法中,在订单存在性验证之后立即进行经销商验证
78 +
79 +### 异常信息
80 +
81 +当经销商不一致时,会抛出包含详细信息的异常:
82 +
83 +```
84 +出库单中的经销商编码[DEALER002]与关联订单[ORDER001]中的经销商编码[DEALER001]不一致
85 +```
86 +
87 +## 测试
88 +
89 +### 单元测试
90 +
91 +创建了 `DeliveryMainServiceImplDealerValidationTest` 测试类,包含以下测试用例:
92 +
93 +1. **成功场景测试**
94 + - 经销商一致时的正常流程
95 + - 验证方法调用正确性
96 +
97 +2. **异常场景测试**
98 + - 经销商不一致时抛出 `DealerMismatchException`
99 + - 订单不存在时抛出 `IllegalArgumentException`
100 +
101 +3. **异常消息测试**
102 + - 验证异常消息格式的正确性
103 +
104 +### 测试覆盖
105 +
106 +- ✅ 新增出库单时的经销商验证
107 +- ✅ 修改出库单时的经销商验证
108 +- ✅ 经销商不一致的异常处理
109 +- ✅ 订单不存在的异常处理
110 +- ✅ 异常消息格式验证
111 +
112 +## 影响范围
113 +
114 +### 修改的文件
115 +
116 +1. `backend/src/main/java/com/apple/erp/exception/DealerMismatchException.java` - 新增异常类
117 +2. `backend/src/main/java/com/apple/erp/service/impl/DeliveryMainServiceImpl.java` - 修改服务实现
118 +3. `backend/src/test/java/com/apple/erp/service/impl/DeliveryMainServiceImplDealerValidationTest.java` - 新增测试类
119 +
120 +### 影响的方法
121 +
122 +- `DeliveryMainServiceImpl.addDelivery()` - 新增经销商验证
123 +- `DeliveryMainServiceImpl.updateDelivery()` - 新增经销商验证
124 +- 新增 `validateDealerConsistency()` 私有方法
125 +
126 +## 业务价值
127 +
128 +### 数据一致性保障
129 +
130 +- 确保出库单与关联订单的经销商信息保持一致
131 +- 防止因经销商信息不匹配导致的数据错误
132 +- 提高系统的数据完整性和可靠性
133 +
134 +### 错误预防
135 +
136 +- 在数据录入阶段就发现并阻止经销商不匹配的问题
137 +- 提供清晰的错误信息,便于用户快速定位和修正问题
138 +- 减少后续业务流程中的数据异常
139 +
140 +### 系统健壮性
141 +
142 +- 增强系统的数据验证能力
143 +- 提供更好的错误处理和用户反馈
144 +- 提高系统的整体稳定性和可维护性
145 +
146 +## 向后兼容性
147 +
148 +- 新增的验证逻辑不会影响现有的正常业务流程
149 +- 只有在经销商不匹配时才会抛出异常
150 +- 保持了原有的方法签名和返回值类型
151 +- 不影响现有的API接口
1 +# 发票验证功能更新
2 +
3 +## 修改概述
4 +
5 +`InvoiceMainServiceImpl` 中的 `addInvoice``updateInvoice` 方法中添加了全面的验证逻辑,确保发票中的经销商与关联订单和出库单中的经销商保持一致。
6 +
7 +## 修改内容
8 +
9 +### 1. 新增自定义异常类
10 +
11 +创建了 `InvoiceValidationException` 异常类,用于处理发票验证失败的情况:
12 +
13 +```java
14 +public class InvoiceValidationException extends RuntimeException {
15 + public InvoiceValidationException(String message) {
16 + super(message);
17 + }
18 +
19 + public InvoiceValidationException(String message, Throwable cause) {
20 + super(message, cause);
21 + }
22 +}
23 +```
24 +
25 +### 2. 扩展DeliveryMainMapper接口
26 +
27 +`DeliveryMainMapper` 接口中添加了 `selectByDeliveryNo` 方法:
28 +
29 +```java
30 +@Select("SELECT * FROM t_delivery_main WHERE delivery_no = #{deliveryNo} AND del_flag = '0'")
31 +DeliveryMain selectByDeliveryNo(@Param("deliveryNo") String deliveryNo);
32 +```
33 +
34 +### 3. 新增验证方法
35 +
36 +`InvoiceMainServiceImpl` 中添加了两个验证方法:
37 +
38 +#### validateOrderConsistency方法
39 +```java
40 +private void validateOrderConsistency(String orderNo, String invoiceDealerCode) {
41 + // 根据订单编号查询订单信息
42 + OrderMain order = orderMainMapper.selectByOrderNo(orderNo);
43 + if (order == null) {
44 + throw new IllegalArgumentException("关联的订单不存在");
45 + }
46 +
47 + // 比较经销商编码
48 + if (!invoiceDealerCode.equals(order.getDealerCode())) {
49 + String errorMessage = String.format(
50 + "发票中的经销商编码[%s]与关联订单[%s]中的经销商编码[%s]不一致",
51 + invoiceDealerCode, orderNo, order.getDealerCode()
52 + );
53 + throw new InvoiceValidationException(errorMessage);
54 + }
55 +}
56 +```
57 +
58 +#### validateDeliveryConsistency方法
59 +```java
60 +private void validateDeliveryConsistency(String deliveryNo, String invoiceDealerCode) {
61 + // 根据出库单编号查询出库单信息
62 + DeliveryMain delivery = deliveryMainMapper.selectByDeliveryNo(deliveryNo);
63 + if (delivery == null) {
64 + throw new IllegalArgumentException("关联的出库单不存在");
65 + }
66 +
67 + // 比较经销商编码
68 + if (!invoiceDealerCode.equals(delivery.getDealerCode())) {
69 + String errorMessage = String.format(
70 + "发票中的经销商编码[%s]与关联出库单[%s]中的经销商编码[%s]不一致",
71 + invoiceDealerCode, deliveryNo, delivery.getDealerCode()
72 + );
73 + throw new InvoiceValidationException(errorMessage);
74 + }
75 +}
76 +```
77 +
78 +### 4. 修改addInvoice方法
79 +
80 +`addInvoice` 方法中添加了验证逻辑:
81 +
82 +```java
83 +// 验证关联的订单是否存在且经销商一致
84 +validateOrderConsistency(addReq.getOrderNo(), addReq.getDealerCode());
85 +
86 +// 验证关联的出库单是否存在且经销商一致(如果提供了出库单号)
87 +if (StringUtils.hasText(addReq.getDeliveryNo())) {
88 + validateDeliveryConsistency(addReq.getDeliveryNo(), addReq.getDealerCode());
89 +}
90 +```
91 +
92 +### 5. 修改updateInvoice方法
93 +
94 +`updateInvoice` 方法中添加了验证逻辑:
95 +
96 +```java
97 +// 验证关联的订单是否存在且经销商一致
98 +validateOrderConsistency(updateReq.getOrderNo(), updateReq.getDealerCode());
99 +
100 +// 验证关联的出库单是否存在且经销商一致(如果提供了出库单号)
101 +if (StringUtils.hasText(updateReq.getDeliveryNo())) {
102 + validateDeliveryConsistency(updateReq.getDeliveryNo(), updateReq.getDealerCode());
103 +}
104 +```
105 +
106 +## 验证逻辑说明
107 +
108 +### 验证流程
109 +
110 +1. **订单存在性验证**:验证关联的订单编号是否在订单管理中存在
111 +2. **订单经销商一致性验证**:验证发票中的经销商编码与关联订单中的经销商编码是否一致
112 +3. **出库单存在性验证**:如果提供了出库单号,验证关联的出库单编号是否在出库管理中存在
113 +4. **出库单经销商一致性验证**:如果提供了出库单号,验证发票中的经销商编码与关联出库单中的经销商编码是否一致
114 +5. **异常处理**:如果任何验证失败,抛出相应的异常
115 +
116 +### 验证时机
117 +
118 +- **新增发票时**:在 `addInvoice` 方法中,在发票编号存在性验证之后立即进行验证
119 +- **修改发票时**:在 `updateInvoice` 方法中,在发票编号存在性验证之后立即进行验证
120 +
121 +### 异常信息
122 +
123 +#### 订单经销商不一致
124 +```
125 +发票中的经销商编码[DEALER002]与关联订单[ORDER001]中的经销商编码[DEALER001]不一致
126 +```
127 +
128 +#### 出库单经销商不一致
129 +```
130 +发票中的经销商编码[DEALER002]与关联出库单[DELIVERY001]中的经销商编码[DEALER001]不一致
131 +```
132 +
133 +#### 订单不存在
134 +```
135 +关联的订单不存在
136 +```
137 +
138 +#### 出库单不存在
139 +```
140 +关联的出库单不存在
141 +```
142 +
143 +## 测试
144 +
145 +### 单元测试
146 +
147 +创建了 `InvoiceMainServiceImplValidationTest` 测试类,包含以下测试用例:
148 +
149 +1. **成功场景测试**
150 + - 经销商一致时的正常流程
151 + - 不提供出库单号时的验证逻辑
152 + - 验证方法调用正确性
153 +
154 +2. **异常场景测试**
155 + - 订单经销商不一致时抛出 `InvoiceValidationException`
156 + - 出库单经销商不一致时抛出 `InvoiceValidationException`
157 + - 订单不存在时抛出 `IllegalArgumentException`
158 + - 出库单不存在时抛出 `IllegalArgumentException`
159 +
160 +3. **异常消息测试**
161 + - 验证异常消息格式的正确性
162 +
163 +### 测试覆盖
164 +
165 +- ✅ 新增发票时的订单验证
166 +- ✅ 新增发票时的出库单验证
167 +- ✅ 修改发票时的订单验证
168 +- ✅ 修改发票时的出库单验证
169 +- ✅ 经销商不一致的异常处理
170 +- ✅ 关联记录不存在的异常处理
171 +- ✅ 异常消息格式验证
172 +
173 +## 影响范围
174 +
175 +### 修改的文件
176 +
177 +1. `backend/src/main/java/com/apple/erp/exception/InvoiceValidationException.java` - 新增异常类
178 +2. `backend/src/main/java/com/apple/erp/mapper/DeliveryMainMapper.java` - 扩展Mapper接口
179 +3. `backend/src/main/java/com/apple/erp/service/impl/InvoiceMainServiceImpl.java` - 修改服务实现
180 +4. `backend/src/test/java/com/apple/erp/service/impl/InvoiceMainServiceImplValidationTest.java` - 新增测试类
181 +
182 +### 影响的方法
183 +
184 +- `InvoiceMainServiceImpl.addInvoice()` - 新增验证逻辑
185 +- `InvoiceMainServiceImpl.updateInvoice()` - 新增验证逻辑
186 +- 新增 `validateOrderConsistency()` 私有方法
187 +- 新增 `validateDeliveryConsistency()` 私有方法
188 +- `DeliveryMainMapper.selectByDeliveryNo()` - 新增查询方法
189 +
190 +## 业务价值
191 +
192 +### 数据一致性保障
193 +
194 +- 确保发票与关联订单的经销商信息保持一致
195 +- 确保发票与关联出库单的经销商信息保持一致
196 +- 防止因经销商信息不匹配导致的数据错误
197 +- 提高系统的数据完整性和可靠性
198 +
199 +### 错误预防
200 +
201 +- 在数据录入阶段就发现并阻止经销商不匹配的问题
202 +- 提供清晰的错误信息,便于用户快速定位和修正问题
203 +- 减少后续业务流程中的数据异常
204 +
205 +### 系统健壮性
206 +
207 +- 增强系统的数据验证能力
208 +- 提供更好的错误处理和用户反馈
209 +- 提高系统的整体稳定性和可维护性
210 +
211 +## 向后兼容性
212 +
213 +- 新增的验证逻辑不会影响现有的正常业务流程
214 +- 只有在经销商不匹配或关联记录不存在时才会抛出异常
215 +- 保持了原有的方法签名和返回值类型
216 +- 不影响现有的API接口
217 +- 出库单号验证是可选的,只有在提供出库单号时才会进行验证
218 +
219 +## 使用示例
220 +
221 +### 成功场景
222 +```java
223 +// 发票中的经销商与订单和出库单中的经销商一致
224 +InvoiceAddReq addReq = new InvoiceAddReq();
225 +addReq.setOrderNo("ORDER001");
226 +addReq.setDeliveryNo("DELIVERY001");
227 +addReq.setDealerCode("DEALER001"); // 与订单和出库单中的经销商一致
228 +
229 +invoiceMainService.addInvoice(addReq); // 验证通过
230 +```
231 +
232 +### 失败场景
233 +```java
234 +// 发票中的经销商与订单中的经销商不一致
235 +InvoiceAddReq addReq = new InvoiceAddReq();
236 +addReq.setOrderNo("ORDER001");
237 +addReq.setDealerCode("DEALER002"); // 与订单中的经销商不一致
238 +
239 +try {
240 + invoiceMainService.addInvoice(addReq);
241 +} catch (InvoiceValidationException e) {
242 + System.out.println("验证失败:" + e.getMessage());
243 + // 输出:验证失败:发票中的经销商编码[DEALER002]与关联订单[ORDER001]中的经销商编码[DEALER001]不一致
244 +}
245 +```
1 +# 返利编号生成规则更新
2 +
3 +## 修改概述
4 +
5 +更新了 `settleIfReady` 方法中返利台账的 `RebateNo` 生成规则,使其符合新的业务需求。
6 +
7 +## 修改内容
8 +
9 +### 1. 数据库表结构更新
10 +
11 +-`t_rebate_detail` 表中添加了 `t_index` 字段
12 +- 字段类型:`INT DEFAULT 0`
13 +- 字段说明:序列号索引(用于生成RebateNo)
14 +
15 +### 2. 实体类更新
16 +
17 +-`Rebate` 实体类中添加了 `tIndex` 属性
18 +- 使用 `@TableField("t_index")` 注解映射数据库字段
19 +
20 +### 3. Mapper接口更新
21 +
22 +-`RebateMapper` 接口中添加了 `getMaxIndexByDate` 方法
23 +- 用于获取指定日期的最大序列号
24 +
25 +### 4. Mapper XML更新
26 +
27 +-`RebateMapper.xml` 中添加了对应的SQL查询
28 +- 更新了 `baseColumns` 包含 `t_index` 字段
29 +
30 +### 5. 服务实现更新
31 +
32 +- 修改了 `RebateSettlementServiceImpl` 中的 `settleIfReady` 方法
33 +- 添加了 `generateRebateNo` 私有方法
34 +- 更新了返利台账生成逻辑
35 +
36 +## 新的RebateNo生成规则
37 +
38 +### 格式
39 +```
40 +RB-YYYYMMDD-00001
41 +```
42 +
43 +### 规则说明
44 +- **前缀**`RB-`
45 +- **日期**`YYYYMMDD` 格式(如:20240115)
46 +- **序列号**:5位数字,从 `00001` 开始
47 +- **序列号递增**:基于 `t_rebate_detail` 表中的 `t_index` 字段
48 +- **每日重置**:序列号每天从1开始重新计数
49 +
50 +### 示例
51 +- 2024年1月15日第一条记录:`RB-20240115-00001`
52 +- 2024年1月15日第二条记录:`RB-20240115-00002`
53 +- 2024年1月16日第一条记录:`RB-20240116-00001`
54 +
55 +## 数据库迁移
56 +
57 +### 新增字段
58 +```sql
59 +ALTER TABLE t_rebate_detail
60 +ADD COLUMN t_index INT DEFAULT 0 COMMENT '序列号索引(用于生成RebateNo)'
61 +AFTER calc_flag;
62 +```
63 +
64 +### 数据初始化
65 +为现有数据按日期分配递增的序列号。
66 +
67 +### 新增索引
68 +```sql
69 +CREATE INDEX idx_rebate_t_index ON t_rebate_detail(t_index);
70 +CREATE INDEX idx_rebate_date_t_index ON t_rebate_detail(rebate_date, t_index);
71 +```
72 +
73 +## 测试
74 +
75 +- 添加了单元测试验证RebateNo生成逻辑
76 +- 测试了不同场景下的序列号生成
77 +- 验证了RebateNo格式的正确性
78 +
79 +## 影响范围
80 +
81 +- `RebateSettlementServiceImpl.settleIfReady()` 方法
82 +- `t_rebate_detail` 表结构
83 +- `Rebate` 实体类
84 +- `RebateMapper` 接口和XML配置
85 +
86 +## 向后兼容性
87 +
88 +- 现有数据通过迁移脚本自动初始化 `t_index`
89 +- 新的生成逻辑不影响现有功能
90 +- 保持了原有的业务逻辑不变
...@@ -229,6 +229,7 @@ CREATE TABLE t_rebate_detail ( ...@@ -229,6 +229,7 @@ CREATE TABLE t_rebate_detail (
229 rebate_date DATE NOT NULL COMMENT '返利记录日期', 229 rebate_date DATE NOT NULL COMMENT '返利记录日期',
230 operate_type TINYINT NOT NULL COMMENT '操作类型(1-新增/2-扣减)', 230 operate_type TINYINT NOT NULL COMMENT '操作类型(1-新增/2-扣减)',
231 calc_flag TINYINT DEFAULT 0 COMMENT '返利计算状态(0-未计算/1-已计算)', 231 calc_flag TINYINT DEFAULT 0 COMMENT '返利计算状态(0-未计算/1-已计算)',
232 + t_index INT DEFAULT 0 COMMENT '序列号索引(用于生成RebateNo)',
232 upload_time DATETIME COMMENT '数据上传时间', 233 upload_time DATETIME COMMENT '数据上传时间',
233 create_by VARCHAR(64) DEFAULT '' COMMENT '创建者', 234 create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
234 create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 235 create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
......
1 +# 出库单经销商验证示例
2 +
3 +## 场景说明
4 +
5 +本示例展示了出库单经销商验证功能的工作方式,包括成功场景和失败场景。
6 +
7 +## 成功场景
8 +
9 +### 场景1:经销商信息一致
10 +
11 +**输入数据:**
12 +- 订单编号:`ORDER001`
13 +- 订单中的经销商编码:`DEALER001`
14 +- 出库单中的经销商编码:`DEALER001`
15 +
16 +**验证结果:** ✅ 通过验证,继续执行出库单创建/修改流程
17 +
18 +**代码示例:**
19 +```java
20 +// 订单信息
21 +OrderMain order = new OrderMain();
22 +order.setOrderNo("ORDER001");
23 +order.setDealerCode("DEALER001");
24 +
25 +// 出库单请求
26 +DeliveryAddReq addReq = new DeliveryAddReq();
27 +addReq.setOrderNo("ORDER001");
28 +addReq.setDealerCode("DEALER001");
29 +
30 +// 验证通过,继续执行
31 +deliveryMainService.addDelivery(addReq);
32 +```
33 +
34 +## 失败场景
35 +
36 +### 场景2:经销商信息不一致
37 +
38 +**输入数据:**
39 +- 订单编号:`ORDER001`
40 +- 订单中的经销商编码:`DEALER001`
41 +- 出库单中的经销商编码:`DEALER002`
42 +
43 +**验证结果:** ❌ 验证失败,抛出 `DealerMismatchException`
44 +
45 +**异常信息:**
46 +```
47 +出库单中的经销商编码[DEALER002]与关联订单[ORDER001]中的经销商编码[DEALER001]不一致
48 +```
49 +
50 +**代码示例:**
51 +```java
52 +// 订单信息
53 +OrderMain order = new OrderMain();
54 +order.setOrderNo("ORDER001");
55 +order.setDealerCode("DEALER001");
56 +
57 +// 出库单请求(经销商编码不匹配)
58 +DeliveryAddReq addReq = new DeliveryAddReq();
59 +addReq.setOrderNo("ORDER001");
60 +addReq.setDealerCode("DEALER002"); // 不匹配的经销商编码
61 +
62 +// 验证失败,抛出异常
63 +try {
64 + deliveryMainService.addDelivery(addReq);
65 +} catch (DealerMismatchException e) {
66 + System.out.println("验证失败:" + e.getMessage());
67 + // 输出:验证失败:出库单中的经销商编码[DEALER002]与关联订单[ORDER001]中的经销商编码[DEALER001]不一致
68 +}
69 +```
70 +
71 +### 场景3:关联订单不存在
72 +
73 +**输入数据:**
74 +- 订单编号:`ORDER999`(不存在的订单)
75 +- 出库单中的经销商编码:`DEALER001`
76 +
77 +**验证结果:** ❌ 验证失败,抛出 `IllegalArgumentException`
78 +
79 +**异常信息:**
80 +```
81 +关联的订单不存在
82 +```
83 +
84 +**代码示例:**
85 +```java
86 +// 出库单请求(关联不存在的订单)
87 +DeliveryAddReq addReq = new DeliveryAddReq();
88 +addReq.setOrderNo("ORDER999"); // 不存在的订单
89 +addReq.setDealerCode("DEALER001");
90 +
91 +// 验证失败,抛出异常
92 +try {
93 + deliveryMainService.addDelivery(addReq);
94 +} catch (IllegalArgumentException e) {
95 + System.out.println("验证失败:" + e.getMessage());
96 + // 输出:验证失败:关联的订单不存在
97 +}
98 +```
99 +
100 +## API调用示例
101 +
102 +### 新增出库单
103 +
104 +**请求:**
105 +```http
106 +POST /api/delivery/add
107 +Content-Type: application/json
108 +
109 +{
110 + "deliveryNo": "DELIVERY001",
111 + "orderNo": "ORDER001",
112 + "dealerCode": "DEALER002", // 与订单中的经销商不一致
113 + "dealerName": "测试经销商",
114 + "deliveryDate": "2024-01-15 10:00:00",
115 + "deliveryItems": [
116 + {
117 + "productCode": "PRODUCT001",
118 + "deliveryQty": 10,
119 + "deliveryAmount": 1000.00
120 + }
121 + ]
122 +}
123 +```
124 +
125 +**响应(失败):**
126 +```http
127 +HTTP/1.1 400 Bad Request
128 +Content-Type: application/json
129 +
130 +{
131 + "code": 400,
132 + "message": "出库单中的经销商编码[DEALER002]与关联订单[ORDER001]中的经销商编码[DEALER001]不一致",
133 + "timestamp": "2024-01-15T10:00:00"
134 +}
135 +```
136 +
137 +### 修改出库单
138 +
139 +**请求:**
140 +```http
141 +PUT /api/delivery/update
142 +Content-Type: application/json
143 +
144 +{
145 + "deliveryId": 1,
146 + "deliveryNo": "DELIVERY001",
147 + "orderNo": "ORDER001",
148 + "dealerCode": "DEALER001", // 与订单中的经销商一致
149 + "dealerName": "测试经销商",
150 + "deliveryDate": "2024-01-15 10:00:00",
151 + "deliveryItems": [
152 + {
153 + "productCode": "PRODUCT001",
154 + "deliveryQty": 10,
155 + "deliveryAmount": 1000.00
156 + }
157 + ]
158 +}
159 +```
160 +
161 +**响应(成功):**
162 +```http
163 +HTTP/1.1 200 OK
164 +Content-Type: application/json
165 +
166 +{
167 + "code": 200,
168 + "message": "出库单修改成功",
169 + "data": true
170 +}
171 +```
172 +
173 +## 前端处理示例
174 +
175 +### JavaScript错误处理
176 +
177 +```javascript
178 +// 新增出库单
179 +async function addDelivery(deliveryData) {
180 + try {
181 + const response = await fetch('/api/delivery/add', {
182 + method: 'POST',
183 + headers: {
184 + 'Content-Type': 'application/json'
185 + },
186 + body: JSON.stringify(deliveryData)
187 + });
188 +
189 + if (!response.ok) {
190 + const errorData = await response.json();
191 + throw new Error(errorData.message);
192 + }
193 +
194 + const result = await response.json();
195 + console.log('出库单创建成功', result);
196 +
197 + } catch (error) {
198 + if (error.message.includes('经销商编码') && error.message.includes('不一致')) {
199 + // 处理经销商不匹配错误
200 + alert('错误:出库单中的经销商与关联订单中的经销商不一致,请检查经销商信息');
201 + } else if (error.message.includes('关联的订单不存在')) {
202 + // 处理订单不存在错误
203 + alert('错误:关联的订单不存在,请检查订单编号');
204 + } else {
205 + // 处理其他错误
206 + alert('出库单创建失败:' + error.message);
207 + }
208 + }
209 +}
210 +```
211 +
212 +## 最佳实践
213 +
214 +### 1. 数据录入时验证
215 +
216 +在用户录入出库单信息时,可以实时验证经销商信息:
217 +
218 +```javascript
219 +// 当订单编号改变时,自动填充经销商信息
220 +async function onOrderNoChange(orderNo) {
221 + if (orderNo) {
222 + try {
223 + const order = await getOrderByNo(orderNo);
224 + if (order) {
225 + // 自动填充经销商信息
226 + document.getElementById('dealerCode').value = order.dealerCode;
227 + document.getElementById('dealerName').value = order.dealerName;
228 + }
229 + } catch (error) {
230 + console.error('获取订单信息失败', error);
231 + }
232 + }
233 +}
234 +```
235 +
236 +### 2. 表单验证
237 +
238 +在提交前进行客户端验证:
239 +
240 +```javascript
241 +function validateDeliveryForm() {
242 + const orderNo = document.getElementById('orderNo').value;
243 + const dealerCode = document.getElementById('dealerCode').value;
244 +
245 + if (orderNo && dealerCode) {
246 + // 可以调用API验证经销商一致性
247 + return validateDealerConsistency(orderNo, dealerCode);
248 + }
249 +
250 + return true;
251 +}
252 +```
253 +
254 +### 3. 错误提示优化
255 +
256 +提供更友好的错误提示:
257 +
258 +```javascript
259 +function handleDealerValidationError(error) {
260 + const errorMessage = error.message;
261 +
262 + if (errorMessage.includes('经销商编码') && errorMessage.includes('不一致')) {
263 + return {
264 + type: 'warning',
265 + title: '经销商信息不匹配',
266 + message: '出库单中的经销商与关联订单中的经销商不一致,请确认经销商信息是否正确',
267 + action: '请检查并修正经销商编码'
268 + };
269 + }
270 +
271 + return {
272 + type: 'error',
273 + title: '验证失败',
274 + message: errorMessage
275 + };
276 +}
277 +```
This diff is collapsed. Click to expand it.