Toggle navigation
Toggle navigation
This project
Loading...
Sign in
zhouhui.jiang
/
test_cursor
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
zhouhui.jiang
2025-10-14 18:13:34 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
732ec79f9e43faaffd88523cb727b29c48d0691b
732ec79f
1 parent
2bf069fa
update 添加验证
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
188 additions
and
17 deletions
backend/src/main/java/com/apple/erp/dto/InvoiceAddReq.java
backend/src/main/java/com/apple/erp/dto/InvoiceUpdateReq.java
backend/src/main/java/com/apple/erp/service/impl/DeliveryMainServiceImpl.java
backend/src/main/java/com/apple/erp/service/impl/InvoiceMainServiceImpl.java
frontend/src/views/delivery/index.vue
frontend/src/views/order/index.vue
tests/api/.token.json
tests/browser/__pycache__/base_test.cpython-37.pyc
backend/src/main/java/com/apple/erp/dto/InvoiceAddReq.java
View file @
732ec79
...
...
@@ -31,7 +31,8 @@ public class InvoiceAddReq {
@Schema
(
description
=
"关联订单编号"
,
required
=
true
)
private
String
orderNo
;
@Schema
(
description
=
"关联出库单编号"
)
@NotBlank
(
message
=
"关联出库单编号不能为空"
)
@Schema
(
description
=
"关联出库单编号"
,
required
=
true
)
private
String
deliveryNo
;
@NotBlank
(
message
=
"经销商编码不能为空"
)
...
...
backend/src/main/java/com/apple/erp/dto/InvoiceUpdateReq.java
View file @
732ec79
...
...
@@ -35,7 +35,8 @@ public class InvoiceUpdateReq {
@Schema
(
description
=
"关联订单编号"
,
required
=
true
)
private
String
orderNo
;
@Schema
(
description
=
"关联出库单编号"
)
@NotBlank
(
message
=
"关联出库单编号不能为空"
)
@Schema
(
description
=
"关联出库单编号"
,
required
=
true
)
private
String
deliveryNo
;
@NotBlank
(
message
=
"经销商编码不能为空"
)
...
...
backend/src/main/java/com/apple/erp/service/impl/DeliveryMainServiceImpl.java
View file @
732ec79
...
...
@@ -2,11 +2,13 @@ package com.apple.erp.service.impl;
import
com.apple.erp.dto.*
;
import
com.apple.erp.entity.DeliveryItem
;
import
com.apple.erp.entity.OrderItem
;
import
com.apple.erp.entity.OrderMain
;
// rebate settlement handled by RebateSettlementService
import
com.apple.erp.service.RebateSettlementService
;
import
com.apple.erp.mapper.DeliveryItemMapper
;
import
com.apple.erp.mapper.OrderMainMapper
;
import
com.apple.erp.mapper.OrderItemMapper
;
// removed unused mapper imports after delegating settlement logic
import
com.apple.erp.util.TimingValidationUtil
;
import
com.apple.erp.exception.DealerMismatchException
;
...
...
@@ -48,6 +50,9 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
private
OrderMainMapper
orderMainMapper
;
@Autowired
private
OrderItemMapper
orderItemMapper
;
@Autowired
private
TimingValidationUtil
timingValidationUtil
;
// rebate settlement is delegated; no local mapper usage required
...
...
@@ -157,6 +162,11 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
}
}
// 业务校验:出库明细需与关联订单的商品至少部分匹配
validateAtLeastOneItemMatchesOrder
(
addReq
.
getOrderNo
(),
addReq
.
getDeliveryItems
()
==
null
?
new
ArrayList
<>()
:
addReq
.
getDeliveryItems
().
stream
().
map
(
DeliveryItemAddReq:
:
getProductCode
).
collect
(
Collectors
.
toList
()));
DeliveryMain
deliveryMain
=
new
DeliveryMain
();
BeanUtils
.
copyProperties
(
addReq
,
deliveryMain
);
deliveryMain
.
setCreateTime
(
LocalDateTime
.
now
());
...
...
@@ -230,6 +240,11 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
}
}
// 业务校验:出库明细需与关联订单的商品至少部分匹配
validateAtLeastOneItemMatchesOrder
(
updateReq
.
getOrderNo
(),
updateReq
.
getDeliveryItems
()
==
null
?
new
ArrayList
<>()
:
updateReq
.
getDeliveryItems
().
stream
().
map
(
DeliveryItemUpdateReq:
:
getProductCode
).
collect
(
Collectors
.
toList
()));
DeliveryMain
deliveryMain
=
new
DeliveryMain
();
BeanUtils
.
copyProperties
(
updateReq
,
deliveryMain
);
deliveryMain
.
setUpdateTime
(
LocalDateTime
.
now
());
...
...
@@ -283,6 +298,39 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
}
}
/**
* 校验至少有一条出库明细与关联订单的商品明细匹配(按产品编码匹配)
* 若完全不匹配则抛出业务异常。
*
* @param orderNo 订单编号
* @param deliveryProductCodes 出库明细中的产品编码列表
*/
private
void
validateAtLeastOneItemMatchesOrder
(
String
orderNo
,
List
<
String
>
deliveryProductCodes
)
{
if
(
deliveryProductCodes
==
null
||
deliveryProductCodes
.
isEmpty
())
{
throw
new
IllegalArgumentException
(
"出库明细不可为空"
);
}
LambdaQueryWrapper
<
OrderItem
>
orderItemWrapper
=
Wrappers
.
lambdaQuery
(
OrderItem
.
class
)
.
eq
(
OrderItem:
:
getOrderNo
,
orderNo
)
.
eq
(
OrderItem:
:
getDelFlag
,
"0"
);
List
<
OrderItem
>
orderItems
=
orderItemMapper
.
selectList
(
orderItemWrapper
);
if
(
orderItems
==
null
||
orderItems
.
isEmpty
())
{
// 订单无商品时,视为不匹配
throw
new
IllegalArgumentException
(
"关联的订单编号,商品明细项为空"
);
}
// 判定是否存在至少一条产品编码匹配
boolean
hasAnyMatch
=
orderItems
.
stream
()
.
map
(
OrderItem:
:
getProductCode
)
.
filter
(
code
->
code
!=
null
&&
!
code
.
isEmpty
())
.
anyMatch
(
deliveryProductCodes:
:
contains
);
if
(!
hasAnyMatch
)
{
throw
new
IllegalArgumentException
(
"出库明细必须至少包含一项与关联订单对应的产品明细,请检查后重新提交"
);
}
}
// 结转逻辑已抽取到 RebateSettlementService
@Override
...
...
backend/src/main/java/com/apple/erp/service/impl/InvoiceMainServiceImpl.java
View file @
732ec79
...
...
@@ -4,11 +4,15 @@ import com.apple.erp.dto.*;
import
com.apple.erp.entity.InvoiceItem
;
import
com.apple.erp.entity.OrderMain
;
import
com.apple.erp.entity.DeliveryMain
;
import
com.apple.erp.entity.OrderItem
;
import
com.apple.erp.entity.DeliveryItem
;
// rebate settlement handled by RebateSettlementService
import
com.apple.erp.service.RebateSettlementService
;
import
com.apple.erp.mapper.InvoiceItemMapper
;
import
com.apple.erp.mapper.OrderMainMapper
;
import
com.apple.erp.mapper.DeliveryMainMapper
;
import
com.apple.erp.mapper.OrderItemMapper
;
import
com.apple.erp.mapper.DeliveryItemMapper
;
// removed unused mapper imports after delegating settlement logic
import
com.apple.erp.util.TimingValidationUtil
;
import
com.apple.erp.exception.InvoiceValidationException
;
...
...
@@ -56,6 +60,12 @@ public class InvoiceMainServiceImpl extends ServiceImpl<InvoiceMainMapper, Invoi
@Autowired
private
DeliveryMainMapper
deliveryMainMapper
;
@Autowired
private
OrderItemMapper
orderItemMapper
;
@Autowired
private
DeliveryItemMapper
deliveryItemMapper
;
// rebate settlement is delegated; no local mapper usage required
@Autowired
...
...
@@ -134,13 +144,23 @@ public class InvoiceMainServiceImpl extends ServiceImpl<InvoiceMainMapper, Invoi
throw
new
IllegalArgumentException
(
"发票编号已存在"
);
}
// 验证关联的订单是否存在且经销商一致
// 关联必填
if
(!
StringUtils
.
hasText
(
addReq
.
getOrderNo
()))
{
throw
new
IllegalArgumentException
(
"关联订单编号不能为空"
);
}
if
(!
StringUtils
.
hasText
(
addReq
.
getDeliveryNo
()))
{
throw
new
IllegalArgumentException
(
"关联出库单编号不能为空"
);
}
// 基础一致性
validateOrderConsistency
(
addReq
.
getOrderNo
(),
addReq
.
getDealerCode
());
validateDeliveryConsistency
(
addReq
.
getDeliveryNo
(),
addReq
.
getDealerCode
());
// 验证关联的出库单是否存在且经销商一致(如果提供了出库单号)
if
(
StringUtils
.
hasText
(
addReq
.
getDeliveryNo
()))
{
validateDeliveryConsistency
(
addReq
.
getDeliveryNo
(),
addReq
.
getDealerCode
());
}
// 收集发票明细产品编码并进行双重“至少一项匹配”校验
List
<
String
>
invoiceProductCodes
=
(
addReq
.
getInvoiceItems
()
==
null
?
java
.
util
.
Collections
.
emptyList
()
:
addReq
.
getInvoiceItems
().
stream
().
map
(
InvoiceItemAddReq:
:
getProductCode
).
collect
(
Collectors
.
toList
()));
validateAtLeastOneItemMatchesOrder
(
addReq
.
getOrderNo
(),
invoiceProductCodes
);
validateAtLeastOneItemMatchesDelivery
(
addReq
.
getDeliveryNo
(),
invoiceProductCodes
);
boolean
isValid
=
true
;
// 时序逻辑验证:开票时间不得早于出库时间
...
...
@@ -206,13 +226,20 @@ public class InvoiceMainServiceImpl extends ServiceImpl<InvoiceMainMapper, Invoi
throw
new
IllegalArgumentException
(
"发票编号已存在"
);
}
// 验证关联的订单是否存在且经销商一致
if
(!
StringUtils
.
hasText
(
updateReq
.
getOrderNo
()))
{
throw
new
IllegalArgumentException
(
"关联订单编号不能为空"
);
}
if
(!
StringUtils
.
hasText
(
updateReq
.
getDeliveryNo
()))
{
throw
new
IllegalArgumentException
(
"关联出库单编号不能为空"
);
}
validateOrderConsistency
(
updateReq
.
getOrderNo
(),
updateReq
.
getDealerCode
());
validateDeliveryConsistency
(
updateReq
.
getDeliveryNo
(),
updateReq
.
getDealerCode
());
// 验证关联的出库单是否存在且经销商一致(如果提供了出库单号)
if
(
StringUtils
.
hasText
(
updateReq
.
getDeliveryNo
()))
{
validateDeliveryConsistency
(
updateReq
.
getDeliveryNo
(),
updateReq
.
getDealerCode
()
);
}
List
<
String
>
invoiceProductCodes
=
(
updateReq
.
getInvoiceItems
()
==
null
?
java
.
util
.
Collections
.
emptyList
()
:
updateReq
.
getInvoiceItems
().
stream
().
map
(
InvoiceItemUpdateReq:
:
getProductCode
).
collect
(
Collectors
.
toList
()));
validateAtLeastOneItemMatchesOrder
(
updateReq
.
getOrderNo
(),
invoiceProductCodes
);
validateAtLeastOneItemMatchesDelivery
(
updateReq
.
getDeliveryNo
(),
invoiceProductCodes
);
boolean
isValid
=
true
;
// 时序逻辑验证:开票时间不得早于出库时间
...
...
@@ -292,6 +319,52 @@ public class InvoiceMainServiceImpl extends ServiceImpl<InvoiceMainMapper, Invoi
}
}
/**
* 校验发票明细至少包含一项与关联订单对应的产品(按产品编码)。
*/
private
void
validateAtLeastOneItemMatchesOrder
(
String
orderNo
,
List
<
String
>
invoiceProductCodes
)
{
if
(
invoiceProductCodes
==
null
||
invoiceProductCodes
.
isEmpty
())
{
throw
new
IllegalArgumentException
(
"发票明细必须至少包含一项与关联订单对应的产品明细,请检查后重新提交"
);
}
LambdaQueryWrapper
<
OrderItem
>
orderItemWrapper
=
Wrappers
.
lambdaQuery
(
OrderItem
.
class
)
.
eq
(
OrderItem:
:
getOrderNo
,
orderNo
)
.
eq
(
OrderItem:
:
getDelFlag
,
"0"
);
List
<
OrderItem
>
orderItems
=
orderItemMapper
.
selectList
(
orderItemWrapper
);
if
(
orderItems
==
null
||
orderItems
.
isEmpty
())
{
throw
new
IllegalArgumentException
(
"发票明细必须至少包含一项与关联订单对应的产品明细,请检查后重新提交"
);
}
boolean
hasAnyMatch
=
orderItems
.
stream
()
.
map
(
OrderItem:
:
getProductCode
)
.
filter
(
code
->
code
!=
null
&&
!
code
.
isEmpty
())
.
anyMatch
(
invoiceProductCodes:
:
contains
);
if
(!
hasAnyMatch
)
{
throw
new
IllegalArgumentException
(
"发票明细必须至少包含一项与关联订单对应的产品明细,请检查后重新提交"
);
}
}
/**
* 校验发票明细至少包含一项与关联出库单对应的产品(按产品编码)。
*/
private
void
validateAtLeastOneItemMatchesDelivery
(
String
deliveryNo
,
List
<
String
>
invoiceProductCodes
)
{
if
(
invoiceProductCodes
==
null
||
invoiceProductCodes
.
isEmpty
())
{
throw
new
IllegalArgumentException
(
"发票明细必须至少包含一项与关联出库单对应的产品明细,请检查后重新提交"
);
}
LambdaQueryWrapper
<
DeliveryItem
>
deliveryItemWrapper
=
Wrappers
.
lambdaQuery
(
DeliveryItem
.
class
)
.
eq
(
DeliveryItem:
:
getDeliveryNo
,
deliveryNo
)
.
eq
(
DeliveryItem:
:
getDelFlag
,
"0"
);
List
<
DeliveryItem
>
deliveryItems
=
deliveryItemMapper
.
selectList
(
deliveryItemWrapper
);
if
(
deliveryItems
==
null
||
deliveryItems
.
isEmpty
())
{
throw
new
IllegalArgumentException
(
"发票明细必须至少包含一项与关联出库单对应的产品明细,请检查后重新提交"
);
}
boolean
hasAnyMatch
=
deliveryItems
.
stream
()
.
map
(
DeliveryItem:
:
getProductCode
)
.
filter
(
code
->
code
!=
null
&&
!
code
.
isEmpty
())
.
anyMatch
(
invoiceProductCodes:
:
contains
);
if
(!
hasAnyMatch
)
{
throw
new
IllegalArgumentException
(
"发票明细必须至少包含一项与关联出库单对应的产品明细,请检查后重新提交"
);
}
}
@Override
@Transactional
public
boolean
deleteInvoice
(
Long
invoiceId
)
{
...
...
frontend/src/views/delivery/index.vue
View file @
732ec79
...
...
@@ -1079,7 +1079,23 @@ const handleSubmitAdd = async () => {
fetchDeliveries() // 刷新列表
} catch (error) {
console.error('新增出库失败:', error)
showMessage('新增出库失败, 请重试', 'error')
// 优先展示后端返回的具体错误信息
const anyError: any = error as any
let errorMessage = '新增出库失败, 请重试'
if (anyError && anyError.response && anyError.response.data) {
const errorData = anyError.response.data
if (typeof errorData.message === 'string' && errorData.message.trim().length > 0) {
errorMessage = errorData.message
}
// 如果有字段级校验错误,拼接展示
if (errorData.data && typeof errorData.data === 'object') {
const fieldErrors = Object.values(errorData.data).filter((msg: any) => typeof msg === 'string') as string[]
if (fieldErrors.length > 0) {
errorMessage = fieldErrors.join('; ')
}
}
}
showMessage(errorMessage, 'error')
}
}
...
...
@@ -1117,7 +1133,23 @@ const handleSubmitEdit = async () => {
fetchDeliveries() // 刷新列表
} catch (error) {
console.error('编辑出库失败:', error)
showMessage('编辑出库失败, 请重试', 'error')
// 优先展示后端返回的具体错误信息
const anyError: any = error as any
let errorMessage = '编辑出库失败, 请重试'
if (anyError && anyError.response && anyError.response.data) {
const errorData = anyError.response.data
if (typeof errorData.message === 'string' && errorData.message.trim().length > 0) {
errorMessage = errorData.message
}
// 如果有字段级校验错误,拼接展示
if (errorData.data && typeof errorData.data === 'object') {
const fieldErrors = Object.values(errorData.data).filter((msg: any) => typeof msg === 'string') as string[]
if (fieldErrors.length > 0) {
errorMessage = fieldErrors.join('; ')
}
}
}
showMessage(errorMessage, 'error')
}
}
...
...
frontend/src/views/order/index.vue
View file @
732ec79
...
...
@@ -991,7 +991,23 @@ const handleSubmit = async () => {
fetchOrders()
} catch (error) {
console.error('保存订单失败:', error)
showMessage('保存订单失败, 请重试', 'error')
// 优先展示后端返回的具体错误信息
const anyError: any = error as any
let errorMessage = '保存订单失败, 请重试'
if (anyError && anyError.response && anyError.response.data) {
const errorData = anyError.response.data
if (typeof errorData.message === 'string' && errorData.message.trim().length > 0) {
errorMessage = errorData.message
}
// 如果有字段级校验错误,拼接展示
if (errorData.data && typeof errorData.data === 'object') {
const fieldErrors = Object.values(errorData.data).filter((msg: any) => typeof msg === 'string') as string[]
if (fieldErrors.length > 0) {
errorMessage = fieldErrors.join('; ')
}
}
}
showMessage(errorMessage, 'error')
}
}
...
...
tests/api/.token.json
View file @
732ec79
{
"token"
:
"eyJhbGciOiJIUzUxMiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwic3ViIjoiYWRtaW4iLCJpYXQiOjE3NjA
zMjY2OTIsImV4cCI6MTc2MDQxMzA5Mn0.g_bbqdvMIIoPws2bOC8AJpFHYo-zSFgybXAEJUF3rvheltchQG1q_jZZSSw2tWLQvZOjzFeJxOROJwKbu5JnIQ
"
,
"saved_at"
:
1760
326692
"token"
:
"eyJhbGciOiJIUzUxMiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwic3ViIjoiYWRtaW4iLCJpYXQiOjE3NjA
0Mjc2NTEsImV4cCI6MTc2MDUxNDA1MX0.vtyVtnZ-g6QJB3CnB9yrB23oLxNkL5doezafBfgyT3byTSUOUD162ZEFw6K3aFT8wYkn25TVyTvLVHY9CufIgg
"
,
"saved_at"
:
1760
427651
}
\ No newline at end of file
...
...
tests/browser/__pycache__/base_test.cpython-37.pyc
View file @
732ec79
No preview for this file type
Please
register
or
login
to post a comment