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-09-29 15:34:21 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
f0d473aa7fee7a8c3f51ce4efda3fb04166ce13f
f0d473aa
1 parent
254c040c
update 订单、出库 、发票
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
989 additions
and
379 deletions
API接口文档.md
backend/src/main/java/com/apple/erp/controller/DealerInfoController.java
backend/src/main/java/com/apple/erp/controller/ProductInfoController.java
backend/src/main/java/com/apple/erp/dto/ProductSimpleRes.java
backend/src/main/java/com/apple/erp/service/DealerInfoService.java
backend/src/main/java/com/apple/erp/service/ProductInfoService.java
backend/src/main/java/com/apple/erp/service/impl/DealerInfoServiceImpl.java
backend/src/main/java/com/apple/erp/service/impl/DeliveryMainServiceImpl.java
backend/src/main/java/com/apple/erp/service/impl/ProductInfoServiceImpl.java
backend/src/main/resources/application.yml
frontend/components.d.ts
frontend/src/api/dealer.ts
frontend/src/api/product.ts
frontend/src/views/delivery/index.vue
frontend/src/views/invoice/index.vue
frontend/src/views/order/index.vue
API接口文档.md
View file @
f0d473a
...
...
@@ -997,6 +997,47 @@
}
```
### 7.9 获取所有产品
**接口路径:**
`GET /api/product/all`
**功能描述:**
获取所有启用的产品信息,用于下拉选择
**权限要求:**
无
**请求参数:**
无
**响应示例:**
```
json
{
"code"
:
200
,
"message"
:
"操作成功"
,
"data"
:
[
{
"productId"
:
1
,
"productCode"
:
"APL-IP15-128G-BK"
,
"productName"
:
"iPhone 15|A2848|iPhone|黑色"
},
{
"productId"
:
2
,
"productCode"
:
"APL-IP15-256G-BK"
,
"productName"
:
"iPhone 15|A2848|iPhone|黑色"
},
{
"productId"
:
3
,
"productCode"
:
"APL-IP15-128G-WH"
,
"productName"
:
"iPhone 15|A2848|iPhone|白色"
}
]
}
```
**响应字段说明:**
-
只返回未删除且销售状态为在售的产品
-
按产品编码升序排列
-
产品名称格式:
`产品名称|产品型号|产品类别|产品颜色`
-
主要用于订单管理等模块的下拉选择
## 8. 经销商管理 (DealerInfoController)
### 8.1 分页查询经销商列表
...
...
@@ -1328,6 +1369,81 @@
}
```
### 8.9 获取所有经销商
**接口路径:**
`GET /api/dealer/all`
**功能描述:**
获取所有启用的经销商信息,用于下拉选择
**权限要求:**
无
**请求参数:**
无
**响应示例:**
```
json
{
"code"
:
200
,
"message"
:
"操作成功"
,
"data"
:
[
{
"dealerId"
:
1
,
"dealerCode"
:
"DL001"
,
"dealerName"
:
"北京经销商"
,
"creditCode"
:
"91110000123456789X"
,
"dealerLevel"
:
1
,
"region"
:
"北京"
,
"contactPerson"
:
"张三"
,
"contactPhone"
:
"13800138000"
,
"cooperateStartDate"
:
"2024-01-01"
,
"cooperateStatus"
:
1
,
"businessLicenseUrl"
:
"https://example.com/license.jpg"
,
"cooperationAgreementUrl"
:
"https://example.com/agreement.pdf"
,
"qualificationAuditStatus"
:
2
,
"auditOpinion"
:
"审核通过"
,
"totalRebateAmount"
:
10000.00
,
"usedRebateAmount"
:
5000.00
,
"pendingRebateAmount"
:
5000.00
,
"lastRebateUpdateTime"
:
"2024-01-01 10:00:00"
,
"createBy"
:
"admin"
,
"createTime"
:
"2024-01-01 10:00:00"
,
"updateBy"
:
"admin"
,
"updateTime"
:
"2024-01-01 10:00:00"
,
"delFlag"
:
"0"
},
{
"dealerId"
:
2
,
"dealerCode"
:
"DL002"
,
"dealerName"
:
"上海经销商"
,
"creditCode"
:
"91310000123456789Y"
,
"dealerLevel"
:
2
,
"region"
:
"上海"
,
"contactPerson"
:
"李四"
,
"contactPhone"
:
"13900139000"
,
"cooperateStartDate"
:
"2024-02-01"
,
"cooperateStatus"
:
1
,
"businessLicenseUrl"
:
"https://example.com/license2.jpg"
,
"cooperationAgreementUrl"
:
"https://example.com/agreement2.pdf"
,
"qualificationAuditStatus"
:
2
,
"auditOpinion"
:
"审核通过"
,
"totalRebateAmount"
:
15000.00
,
"usedRebateAmount"
:
8000.00
,
"pendingRebateAmount"
:
7000.00
,
"lastRebateUpdateTime"
:
"2024-02-01 10:00:00"
,
"createBy"
:
"admin"
,
"createTime"
:
"2024-02-01 10:00:00"
,
"updateBy"
:
"admin"
,
"updateTime"
:
"2024-02-01 10:00:00"
,
"delFlag"
:
"0"
}
]
}
```
**响应字段说明:**
-
只返回未删除且合作状态为正常的经销商
-
按经销商编码升序排列
-
主要用于订单管理等模块的下拉选择
## 数据字典
### 销售状态(saleStatus)
...
...
backend/src/main/java/com/apple/erp/controller/DealerInfoController.java
View file @
f0d473a
...
...
@@ -33,6 +33,13 @@ public class DealerInfoController {
return
ApiRes
.
success
(
page
);
}
@Operation
(
summary
=
"获取所有经销商"
,
description
=
"获取所有启用的经销商信息,用于下拉选择"
)
@GetMapping
(
"/all"
)
public
ApiRes
<
List
<
DealerInfo
>>
getAllDealers
()
{
List
<
DealerInfo
>
dealers
=
dealerInfoService
.
getAllDealers
();
return
ApiRes
.
success
(
dealers
);
}
@Operation
(
summary
=
"获取经销商详情"
,
description
=
"根据经销商ID获取经销商详细信息"
)
@GetMapping
(
"/{dealerId}"
)
@PreAuthorize
(
"hasAuthority('dealer:detail')"
)
...
...
backend/src/main/java/com/apple/erp/controller/ProductInfoController.java
View file @
f0d473a
...
...
@@ -3,6 +3,7 @@ package com.apple.erp.controller;
import
com.apple.erp.dto.ProductAddReq
;
import
com.apple.erp.dto.ProductQueryReq
;
import
com.apple.erp.dto.ProductUpdateReq
;
import
com.apple.erp.dto.ProductSimpleRes
;
import
com.apple.erp.entity.ProductInfo
;
import
com.apple.erp.service.ProductInfoService
;
import
com.apple.erp.dto.response.ApiRes
;
...
...
@@ -174,4 +175,15 @@ public class ProductInfoController {
return
ApiRes
.
error
(
"修改返利标识失败:"
+
e
.
getMessage
());
}
}
@Operation
(
summary
=
"获取所有产品"
,
description
=
"获取所有启用的产品信息,用于下拉选择"
)
@GetMapping
(
"/all"
)
public
ApiRes
<
List
<
ProductSimpleRes
>>
getAllProducts
()
{
try
{
List
<
ProductSimpleRes
>
products
=
productInfoService
.
getAllProducts
();
return
ApiRes
.
success
(
products
);
}
catch
(
Exception
e
)
{
return
ApiRes
.
error
(
"获取产品列表失败:"
+
e
.
getMessage
());
}
}
}
...
...
backend/src/main/java/com/apple/erp/dto/ProductSimpleRes.java
0 → 100644
View file @
f0d473a
package
com
.
apple
.
erp
.
dto
;
import
io.swagger.v3.oas.annotations.media.Schema
;
import
lombok.Data
;
/**
* 产品简单信息响应DTO
* 用于下拉选择等场景
*
* @author Apple ERP Team
* @since 2025-01-27
*/
@Data
@Schema
(
description
=
"产品简单信息"
)
public
class
ProductSimpleRes
{
@Schema
(
description
=
"产品ID"
)
private
Long
productId
;
@Schema
(
description
=
"产品编码"
)
private
String
productCode
;
@Schema
(
description
=
"产品名称"
)
private
String
productName
;
}
backend/src/main/java/com/apple/erp/service/DealerInfoService.java
View file @
f0d473a
...
...
@@ -69,4 +69,10 @@ public interface DealerInfoService extends IService<DealerInfo> {
* @return 是否成功
*/
boolean
updateDealerAuditStatus
(
Long
dealerId
,
Integer
qualificationAuditStatus
,
String
auditOpinion
);
/**
* 获取所有启用的经销商
* @return 经销商列表
*/
List
<
DealerInfo
>
getAllDealers
();
}
...
...
backend/src/main/java/com/apple/erp/service/ProductInfoService.java
View file @
f0d473a
...
...
@@ -3,6 +3,7 @@ package com.apple.erp.service;
import
com.apple.erp.dto.ProductAddReq
;
import
com.apple.erp.dto.ProductQueryReq
;
import
com.apple.erp.dto.ProductUpdateReq
;
import
com.apple.erp.dto.ProductSimpleRes
;
import
com.apple.erp.entity.ProductInfo
;
import
com.baomidou.mybatisplus.core.metadata.IPage
;
import
com.baomidou.mybatisplus.extension.service.IService
;
...
...
@@ -91,4 +92,11 @@ public interface ProductInfoService extends IService<ProductInfo> {
* @return 是否存在
*/
boolean
checkProductCodeExists
(
String
productCode
,
Long
productId
);
/**
* 获取所有启用的产品简单信息
*
* @return 产品简单信息列表
*/
List
<
ProductSimpleRes
>
getAllProducts
();
}
...
...
backend/src/main/java/com/apple/erp/service/impl/DealerInfoServiceImpl.java
View file @
f0d473a
...
...
@@ -234,4 +234,13 @@ public class DealerInfoServiceImpl extends ServiceImpl<DealerInfoMapper, DealerI
}
return
count
(
queryWrapper
)
>
0
;
}
@Override
public
List
<
DealerInfo
>
getAllDealers
()
{
LambdaQueryWrapper
<
DealerInfo
>
queryWrapper
=
new
LambdaQueryWrapper
<>();
queryWrapper
.
eq
(
DealerInfo:
:
getDelFlag
,
"0"
)
// 未删除
.
eq
(
DealerInfo:
:
getCooperateStatus
,
1
)
// 正常合作状态
.
orderByAsc
(
DealerInfo:
:
getDealerCode
);
// 按经销商编码排序
return
list
(
queryWrapper
);
}
}
...
...
backend/src/main/java/com/apple/erp/service/impl/DeliveryMainServiceImpl.java
View file @
f0d473a
package
com
.
apple
.
erp
.
service
.
impl
;
import
com.apple.erp.dto.*
;
import
com.apple.erp.entity.DeliveryItem
;
import
com.apple.erp.mapper.DeliveryItemMapper
;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
com.baomidou.mybatisplus.core.metadata.IPage
;
import
com.baomidou.mybatisplus.core.toolkit.Wrappers
;
import
com.baomidou.mybatisplus.extension.plugins.pagination.Page
;
import
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
;
import
com.apple.erp.entity.DeliveryMain
;
import
com.apple.erp.mapper.DeliveryMainMapper
;
import
com.apple.erp.service.DeliveryMainService
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.BeanUtils
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
org.springframework.util.StringUtils
;
import
java.time.LocalDateTime
;
import
java.time.format.DateTimeFormatter
;
import
java.util.List
;
import
java.util.stream.Collectors
;
/**
* 出库主表Service实现类
*
* @author Apple ERP System
* @since 2025-01-01
*/
@Slf4j
@Service
public
class
DeliveryMainServiceImpl
extends
ServiceImpl
<
DeliveryMainMapper
,
DeliveryMain
>
implements
DeliveryMainService
{
@Autowired
private
DeliveryItemMapper
deliveryItemMapper
;
private
static
final
DateTimeFormatter
DATE_TIME_FORMATTER
=
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd HH:mm:ss"
);
@Override
public
Page
<
DeliveryRes
>
getDeliveryList
(
DeliveryQueryReq
queryReq
)
{
LambdaQueryWrapper
<
DeliveryMain
>
queryWrapper
=
Wrappers
.
lambdaQuery
(
DeliveryMain
.
class
)
.
eq
(
StringUtils
.
hasText
(
queryReq
.
getDeliveryNo
()),
DeliveryMain:
:
getDeliveryNo
,
queryReq
.
getDeliveryNo
())
.
eq
(
StringUtils
.
hasText
(
queryReq
.
getDealerCode
()),
DeliveryMain:
:
getDealerCode
,
queryReq
.
getDealerCode
())
.
like
(
StringUtils
.
hasText
(
queryReq
.
getDealerName
()),
DeliveryMain:
:
getDealerName
,
queryReq
.
getDealerName
())
.
eq
(
StringUtils
.
hasText
(
queryReq
.
getOrderNo
()),
DeliveryMain:
:
getOrderNo
,
queryReq
.
getOrderNo
())
.
eq
(
queryReq
.
getDeliveryStatus
()
!=
null
,
DeliveryMain:
:
getDeliveryStatus
,
queryReq
.
getDeliveryStatus
())
.
eq
(
StringUtils
.
hasText
(
queryReq
.
getWarehouseCode
()),
DeliveryMain:
:
getWarehouseCode
,
queryReq
.
getWarehouseCode
())
.
eq
(
StringUtils
.
hasText
(
queryReq
.
getDataSource
()),
DeliveryMain:
:
getDataSource
,
queryReq
.
getDataSource
())
.
ge
(
StringUtils
.
hasText
(
queryReq
.
getDeliveryStartDate
()),
DeliveryMain:
:
getDeliveryDate
,
parseDateTime
(
queryReq
.
getDeliveryStartDate
()))
.
le
(
StringUtils
.
hasText
(
queryReq
.
getDeliveryEndDate
()),
DeliveryMain:
:
getDeliveryDate
,
parseDateTime
(
queryReq
.
getDeliveryEndDate
()))
.
eq
(
DeliveryMain:
:
getDelFlag
,
"0"
)
.
orderByDesc
(
DeliveryMain:
:
getCreateTime
);
Page
<
DeliveryMain
>
page
=
new
Page
<>(
queryReq
.
getPageNum
(),
queryReq
.
getPageSize
());
IPage
<
DeliveryMain
>
deliveryMainPage
=
baseMapper
.
selectPage
(
page
,
queryWrapper
);
List
<
DeliveryRes
>
deliveryResList
=
deliveryMainPage
.
getRecords
().
stream
().
map
(
deliveryMain
->
{
DeliveryRes
deliveryRes
=
new
DeliveryRes
();
BeanUtils
.
copyProperties
(
deliveryMain
,
deliveryRes
);
// 查找出库明细
LambdaQueryWrapper
<
DeliveryItem
>
itemQueryWrapper
=
Wrappers
.
lambdaQuery
(
DeliveryItem
.
class
)
.
eq
(
DeliveryItem:
:
getDeliveryId
,
deliveryMain
.
getDeliveryId
());
List
<
DeliveryItem
>
deliveryItems
=
deliveryItemMapper
.
selectList
(
itemQueryWrapper
);
List
<
DeliveryItemRes
>
itemResList
=
deliveryItems
.
stream
().
map
(
deliveryItem
->
{
DeliveryItemRes
itemRes
=
new
DeliveryItemRes
();
BeanUtils
.
copyProperties
(
deliveryItem
,
itemRes
);
return
itemRes
;
}).
collect
(
Collectors
.
toList
());
deliveryRes
.
setDeliveryItems
(
itemResList
);
return
deliveryRes
;
}).
collect
(
Collectors
.
toList
());
Page
<
DeliveryRes
>
resultPage
=
new
Page
<>(
deliveryMainPage
.
getCurrent
(),
deliveryMainPage
.
getSize
(),
deliveryMainPage
.
getTotal
());
resultPage
.
setRecords
(
deliveryResList
);
return
resultPage
;
}
@Override
public
DeliveryRes
getDeliveryDetail
(
Long
deliveryId
)
{
DeliveryMain
deliveryMain
=
baseMapper
.
selectById
(
deliveryId
);
if
(
deliveryMain
==
null
||
"2"
.
equals
(
deliveryMain
.
getDelFlag
()))
{
return
null
;
}
DeliveryRes
deliveryRes
=
new
DeliveryRes
();
BeanUtils
.
copyProperties
(
deliveryMain
,
deliveryRes
);
LambdaQueryWrapper
<
DeliveryItem
>
itemQueryWrapper
=
Wrappers
.
lambdaQuery
(
DeliveryItem
.
class
)
.
eq
(
DeliveryItem:
:
getDeliveryId
,
deliveryId
);
List
<
DeliveryItem
>
deliveryItems
=
deliveryItemMapper
.
selectList
(
itemQueryWrapper
);
List
<
DeliveryItemRes
>
itemResList
=
deliveryItems
.
stream
().
map
(
deliveryItem
->
{
DeliveryItemRes
itemRes
=
new
DeliveryItemRes
();
BeanUtils
.
copyProperties
(
deliveryItem
,
itemRes
);
return
itemRes
;
}).
collect
(
Collectors
.
toList
());
deliveryRes
.
setDeliveryItems
(
itemResList
);
return
deliveryRes
;
}
@Override
@Transactional
public
boolean
addDelivery
(
DeliveryAddReq
addReq
)
{
// 检查出库单编号是否已存在
if
(
isDeliveryNoExist
(
addReq
.
getDeliveryNo
(),
null
))
{
throw
new
IllegalArgumentException
(
"出库单编号已存在"
);
}
DeliveryMain
deliveryMain
=
new
DeliveryMain
();
BeanUtils
.
copyProperties
(
addReq
,
deliveryMain
);
deliveryMain
.
setCreateTime
(
LocalDateTime
.
now
());
deliveryMain
.
setUpdateTime
(
LocalDateTime
.
now
());
if
(
StringUtils
.
hasText
(
addReq
.
getDeliveryDate
()))
{
deliveryMain
.
setDeliveryDate
(
parseDateTime
(
addReq
.
getDeliveryDate
()));
}
deliveryMain
.
setDelFlag
(
"0"
);
int
insert
=
baseMapper
.
insert
(
deliveryMain
);
if
(
insert
>
0
)
{
for
(
DeliveryItemAddReq
itemAddReq
:
addReq
.
getDeliveryItems
())
{
DeliveryItem
deliveryItem
=
new
DeliveryItem
();
BeanUtils
.
copyProperties
(
itemAddReq
,
deliveryItem
);
deliveryItem
.
setDeliveryId
(
deliveryMain
.
getDeliveryId
());
deliveryItem
.
setDeliveryNo
(
deliveryMain
.
getDeliveryNo
());
deliveryItem
.
setOrderNo
(
deliveryMain
.
getOrderNo
());
deliveryItem
.
setCreateTime
(
LocalDateTime
.
now
());
deliveryItem
.
setUpdateTime
(
LocalDateTime
.
now
());
deliveryItem
.
setDelFlag
(
"0"
);
deliveryItemMapper
.
insert
(
deliveryItem
);
}
return
true
;
}
return
false
;
}
@Override
@Transactional
public
boolean
updateDelivery
(
DeliveryUpdateReq
updateReq
)
{
// 检查出库单编号是否已存在且不属于当前出库单
if
(
isDeliveryNoExist
(
updateReq
.
getDeliveryNo
(),
updateReq
.
getDeliveryId
()))
{
throw
new
IllegalArgumentException
(
"出库单编号已存在"
);
}
DeliveryMain
deliveryMain
=
new
DeliveryMain
();
BeanUtils
.
copyProperties
(
updateReq
,
deliveryMain
);
deliveryMain
.
setUpdateTime
(
LocalDateTime
.
now
());
int
update
=
baseMapper
.
updateById
(
deliveryMain
);
if
(
update
>
0
)
{
// 先删除旧的出库明细
LambdaQueryWrapper
<
DeliveryItem
>
deleteWrapper
=
Wrappers
.
lambdaQuery
(
DeliveryItem
.
class
)
.
eq
(
DeliveryItem:
:
getDeliveryId
,
updateReq
.
getDeliveryId
());
deliveryItemMapper
.
delete
(
deleteWrapper
);
// 插入新的出库明细
for
(
DeliveryItemUpdateReq
itemUpdateReq
:
updateReq
.
getDeliveryItems
())
{
DeliveryItem
deliveryItem
=
new
DeliveryItem
();
BeanUtils
.
copyProperties
(
itemUpdateReq
,
deliveryItem
);
deliveryItem
.
setDeliveryId
(
updateReq
.
getDeliveryId
());
deliveryItem
.
setDeliveryNo
(
updateReq
.
getDeliveryNo
());
deliveryItem
.
setOrderNo
(
updateReq
.
getOrderNo
());
deliveryItem
.
setCreateTime
(
LocalDateTime
.
now
());
// 新增明细时设置创建时间
deliveryItem
.
setUpdateTime
(
LocalDateTime
.
now
());
deliveryItem
.
setDelFlag
(
"0"
);
deliveryItemMapper
.
insert
(
deliveryItem
);
}
return
true
;
}
return
false
;
}
@Override
@Transactional
public
boolean
deleteDelivery
(
Long
deliveryId
)
{
DeliveryMain
deliveryMain
=
new
DeliveryMain
();
deliveryMain
.
setDeliveryId
(
deliveryId
);
deliveryMain
.
setDelFlag
(
"2"
);
// 逻辑删除
deliveryMain
.
setUpdateTime
(
LocalDateTime
.
now
());
return
baseMapper
.
updateById
(
deliveryMain
)
>
0
;
}
@Override
@Transactional
public
boolean
batchDeleteDeliveries
(
List
<
Long
>
deliveryIds
)
{
List
<
DeliveryMain
>
deliveriesToUpdate
=
deliveryIds
.
stream
().
map
(
deliveryId
->
{
DeliveryMain
deliveryMain
=
new
DeliveryMain
();
deliveryMain
.
setDeliveryId
(
deliveryId
);
deliveryMain
.
setDelFlag
(
"2"
);
deliveryMain
.
setUpdateTime
(
LocalDateTime
.
now
());
return
deliveryMain
;
}).
collect
(
Collectors
.
toList
());
return
updateBatchById
(
deliveriesToUpdate
);
}
@Override
public
boolean
updateDeliveryStatus
(
Long
deliveryId
,
Integer
deliveryStatus
)
{
DeliveryMain
deliveryMain
=
new
DeliveryMain
();
deliveryMain
.
setDeliveryId
(
deliveryId
);
deliveryMain
.
setDeliveryStatus
(
deliveryStatus
);
deliveryMain
.
setUpdateTime
(
LocalDateTime
.
now
());
return
baseMapper
.
updateById
(
deliveryMain
)
>
0
;
}
/**
* 检查出库单编号是否已存在
* @param deliveryNo 出库单编号
* @param excludeDeliveryId 排除的出库单ID(用于修改时)
* @return 是否存在
*/
private
boolean
isDeliveryNoExist
(
String
deliveryNo
,
Long
excludeDeliveryId
)
{
LambdaQueryWrapper
<
DeliveryMain
>
queryWrapper
=
Wrappers
.
lambdaQuery
(
DeliveryMain
.
class
)
.
eq
(
DeliveryMain:
:
getDeliveryNo
,
deliveryNo
)
.
eq
(
DeliveryMain:
:
getDelFlag
,
"0"
);
if
(
excludeDeliveryId
!=
null
)
{
queryWrapper
.
ne
(
DeliveryMain:
:
getDeliveryId
,
excludeDeliveryId
);
}
return
baseMapper
.
selectCount
(
queryWrapper
)
>
0
;
}
private
LocalDateTime
parseDateTime
(
String
dateTimeStr
)
{
if
(!
StringUtils
.
hasText
(
dateTimeStr
))
{
return
null
;
}
try
{
return
LocalDateTime
.
parse
(
dateTimeStr
,
DATE_TIME_FORMATTER
);
}
catch
(
Exception
e
)
{
log
.
warn
(
"日期时间解析失败: {}"
,
dateTimeStr
,
e
);
return
null
;
}
}
}
...
...
backend/src/main/java/com/apple/erp/service/impl/ProductInfoServiceImpl.java
View file @
f0d473a
...
...
@@ -3,6 +3,7 @@ package com.apple.erp.service.impl;
import
com.apple.erp.dto.ProductAddReq
;
import
com.apple.erp.dto.ProductQueryReq
;
import
com.apple.erp.dto.ProductUpdateReq
;
import
com.apple.erp.dto.ProductSimpleRes
;
import
com.apple.erp.entity.ProductInfo
;
import
com.apple.erp.mapper.ProductInfoMapper
;
import
com.apple.erp.service.ProductInfoService
;
...
...
@@ -192,4 +193,40 @@ public class ProductInfoServiceImpl extends ServiceImpl<ProductInfoMapper, Produ
return
queryWrapper
;
}
@Override
public
List
<
ProductSimpleRes
>
getAllProducts
()
{
LambdaQueryWrapper
<
ProductInfo
>
queryWrapper
=
new
LambdaQueryWrapper
<>();
queryWrapper
.
eq
(
ProductInfo:
:
getDelFlag
,
"0"
)
// 未删除
.
eq
(
ProductInfo:
:
getSaleStatus
,
1
)
// 在售状态
.
orderByAsc
(
ProductInfo:
:
getProductCode
);
// 按产品编码排序
List
<
ProductInfo
>
products
=
list
(
queryWrapper
);
return
products
.
stream
()
.
map
(
product
->
{
ProductSimpleRes
res
=
new
ProductSimpleRes
();
res
.
setProductId
(
product
.
getProductId
());
res
.
setProductCode
(
product
.
getProductCode
());
// 拼接产品名称:产品名称|产品型号|产品类别|产品颜色
StringBuilder
productNameBuilder
=
new
StringBuilder
();
productNameBuilder
.
append
(
product
.
getProductName
()
!=
null
?
product
.
getProductName
()
:
""
);
if
(
product
.
getProductModel
()
!=
null
&&
!
product
.
getProductModel
().
trim
().
isEmpty
())
{
productNameBuilder
.
append
(
"|"
).
append
(
product
.
getProductModel
());
}
if
(
product
.
getProductType
()
!=
null
&&
!
product
.
getProductType
().
trim
().
isEmpty
())
{
productNameBuilder
.
append
(
"|"
).
append
(
product
.
getProductType
());
}
if
(
product
.
getColor
()
!=
null
&&
!
product
.
getColor
().
trim
().
isEmpty
())
{
productNameBuilder
.
append
(
"|"
).
append
(
product
.
getColor
());
}
res
.
setProductName
(
productNameBuilder
.
toString
());
return
res
;
})
.
collect
(
java
.
util
.
stream
.
Collectors
.
toList
());
}
}
...
...
backend/src/main/resources/application.yml
View file @
f0d473a
...
...
@@ -17,8 +17,8 @@ spring:
type
:
com.alibaba.druid.pool.DruidDataSource
driver-class-name
:
com.mysql.cj.jdbc.Driver
url
:
jdbc:mysql://localhost:3306/apple_erp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username
:
apple_erp_app
password
:
StrongPassw0rd!
username
:
root
password
:
MySQL@123
druid
:
# 初始连接数
initial-size
:
5
...
...
@@ -70,7 +70,7 @@ spring:
redis
:
host
:
localhost
port
:
6379
password
:
123456
password
:
database
:
0
timeout
:
10000ms
lettuce
:
...
...
frontend/components.d.ts
View file @
f0d473a
...
...
@@ -7,10 +7,30 @@ export {}
declare
module
'vue'
{
export
interface
GlobalComponents
{
ElButton
:
typeof
import
(
'element-plus/es'
)[
'ElButton'
]
ElDatePicker
:
typeof
import
(
'element-plus/es'
)[
'ElDatePicker'
]
ElDescriptions
:
typeof
import
(
'element-plus/es'
)[
'ElDescriptions'
]
ElDescriptionsItem
:
typeof
import
(
'element-plus/es'
)[
'ElDescriptionsItem'
]
ElDialog
:
typeof
import
(
'element-plus/es'
)[
'ElDialog'
]
ElForm
:
typeof
import
(
'element-plus/es'
)[
'ElForm'
]
ElFormItem
:
typeof
import
(
'element-plus/es'
)[
'ElFormItem'
]
ElInput
:
typeof
import
(
'element-plus/es'
)[
'ElInput'
]
ElInputNumber
:
typeof
import
(
'element-plus/es'
)[
'ElInputNumber'
]
ElOption
:
typeof
import
(
'element-plus/es'
)[
'ElOption'
]
ElPagination
:
typeof
import
(
'element-plus/es'
)[
'ElPagination'
]
ElRadio
:
typeof
import
(
'element-plus/es'
)[
'ElRadio'
]
ElRadioGroup
:
typeof
import
(
'element-plus/es'
)[
'ElRadioGroup'
]
ElSelect
:
typeof
import
(
'element-plus/es'
)[
'ElSelect'
]
ElTable
:
typeof
import
(
'element-plus/es'
)[
'ElTable'
]
ElTableColumn
:
typeof
import
(
'element-plus/es'
)[
'ElTableColumn'
]
ElTag
:
typeof
import
(
'element-plus/es'
)[
'ElTag'
]
Header
:
typeof
import
(
'./src/components/layout/Header.vue'
)[
'default'
]
RouterLink
:
typeof
import
(
'vue-router'
)[
'RouterLink'
]
RouterView
:
typeof
import
(
'vue-router'
)[
'RouterView'
]
Sidebar
:
typeof
import
(
'./src/components/layout/Sidebar.vue'
)[
'default'
]
SidebarItem
:
typeof
import
(
'./src/components/layout/SidebarItem.vue'
)[
'default'
]
}
export
interface
ComponentCustomProperties
{
vLoading
:
typeof
import
(
'element-plus/es'
)[
'ElLoadingDirective'
]
}
}
...
...
frontend/src/api/dealer.ts
View file @
f0d473a
...
...
@@ -162,5 +162,10 @@ export const dealerApi = {
return
api
.
post
<
ApiResponse
>
(
`/dealer/
${
dealerId
}
/auditStatus`
,
null
,
{
params
:
{
qualificationAuditStatus
,
auditOpinion
}
})
},
// 获取所有经销商(用于下拉选择)
getAllDealers
:
()
=>
{
return
api
.
get
<
ApiResponse
<
DealerInfo
[]
>>
(
'/dealer/all'
)
}
}
...
...
frontend/src/api/product.ts
View file @
f0d473a
...
...
@@ -55,6 +55,13 @@ export interface ProductInfo {
updateTime
:
string
}
// 产品简单信息接口(用于下拉选择)
export
interface
ProductSimpleRes
{
productId
:
number
productCode
:
string
productName
:
string
}
// 产品查询请求参数
export
interface
ProductQueryReq
{
productCode
?:
string
...
...
@@ -157,5 +164,10 @@ export const productApi = {
// 修改返利标识
updateRebateFlag
:
(
productId
:
number
,
rebateFlag
:
number
)
=>
{
return
api
.
post
(
`/product/
${
productId
}
/rebate`
,
null
,
{
params
:
{
rebateFlag
}
})
},
// 获取所有产品(用于下拉选择)
getAllProducts
:
()
=>
{
return
api
.
get
<
ApiResponse
<
ProductSimpleRes
[]
>>
(
'/product/all'
)
}
}
...
...
frontend/src/views/delivery/index.vue
View file @
f0d473a
...
...
@@ -4,11 +4,11 @@
<div class="search-section">
<div class="search-row">
<div class="search-item">
<label>
订
单编号:</label>
<label>
出库
单编号:</label>
<input
v-model="searchParams.
order
No"
v-model="searchParams.
delivery
No"
class="search-input"
placeholder="请输入
订
单编号"
placeholder="请输入
出库
单编号"
/>
</div>
<div class="search-item">
...
...
@@ -38,17 +38,17 @@
</div>
<div class="search-row">
<div class="search-item">
<label>开始日期:</label>
<label>
出库
开始日期:</label>
<input
v-model="searchParams.
s
tartDate"
v-model="searchParams.
deliveryS
tartDate"
class="search-input"
type="date"
/>
</div>
<div class="search-item">
<label>结束日期:</label>
<label>
出库
结束日期:</label>
<input
v-model="searchParams.
e
ndDate"
v-model="searchParams.
deliveryE
ndDate"
class="search-input"
type="date"
/>
...
...
@@ -113,6 +113,7 @@
<td>{{ formatTime(delivery.createTime || '') }}</td>
<td class="action-col">
<button @click="handleView(delivery)" class="view-btn">👁️ 查看</button>
<button @click="handleEdit(delivery)" class="edit-btn">✏️ 编辑</button>
<button @click="handleDelete(delivery)" class="delete-btn">🗑️ 删除</button>
</td>
</tr>
...
...
@@ -179,15 +180,11 @@
<div class="detail-grid">
<div class="detail-item">
<label>出库单ID:</label>
<span>{{
order
Detail.deliveryId }}</span>
<span>{{
delivery
Detail.deliveryId }}</span>
</div>
<div class="detail-item">
<label>出库单编号:</label>
<span>{{ orderDetail.deliveryNo }}</span>
</div>
<div class="detail-item">
<label>关联订单编号:</label>
<span>{{ orderDetail.orderNo }}</span>
<span>{{ deliveryDetail.deliveryNo }}</span>
</div>
<div class="detail-item">
<label>关联订单编号:</label>
...
...
@@ -203,7 +200,7 @@
</div>
<div class="detail-item">
<label>出库日期:</label>
<span>{{ formatDate(
order
Detail.deliveryDate) }}</span>
<span>{{ formatDate(
delivery
Detail.deliveryDate) }}</span>
</div>
<div class="detail-item">
<label>出库状态:</label>
...
...
@@ -213,43 +210,43 @@
</div>
<div class="detail-item">
<label>仓库编码:</label>
<span>{{
order
Detail.warehouseCode }}</span>
<span>{{
delivery
Detail.warehouseCode }}</span>
</div>
<div class="detail-item">
<label>数据来源:</label>
<span>{{
order
Detail.dataSource || '-' }}</span>
<span>{{
delivery
Detail.dataSource || '-' }}</span>
</div>
<div class="detail-item">
<label>上传时间:</label>
<span>{{ formatTime(
order
Detail.uploadTime || '') }}</span>
<span>{{ formatTime(
delivery
Detail.uploadTime || '') }}</span>
</div>
<div class="detail-item">
<label>创建时间:</label>
<span>{{ formatTime(
order
Detail.createTime || '') }}</span>
<span>{{ formatTime(
delivery
Detail.createTime || '') }}</span>
</div>
<div class="detail-item">
<label>更新时间:</label>
<span>{{ formatTime(
order
Detail.updateTime || '') }}</span>
<span>{{ formatTime(
delivery
Detail.updateTime || '') }}</span>
</div>
</div>
</div>
<div class="detail-section" v-if="
orderDetail.deliveryItems && order
Detail.deliveryItems.length > 0">
<div class="detail-section" v-if="
deliveryDetail.deliveryItems && delivery
Detail.deliveryItems.length > 0">
<h4>出库明细</h4>
<table class="detail-table">
<thead>
<tr>
<th>产品编码</th>
<th>产品名称</th>
<th>产品编码</th>
<th>出库数量</th>
<th>出库单价</th>
<th>出库金额</th>
</tr>
</thead>
<tbody>
<tr v-for="item in orderDetail.deliveryItems" :key="item.deliveryItemId">
<td>{{ item.productCode }}</td>
<tr v-for="item in deliveryDetail.deliveryItems" :key="item.deliveryItemId">
<td>{{ item.productName }}</td>
<td>{{ item.productCode }}</td>
<td>{{ item.deliveryQty }}</td>
<td>{{ formatCurrency(item.deliveryPrice) }}</td>
<td>{{ formatCurrency(item.deliveryAmount) }}</td>
...
...
@@ -262,15 +259,15 @@
</div>
</div>
<!--
新增
出库对话框 -->
<div v-if="show
AddDialog" class="dialog-overlay" @click="closeAdd
Dialog">
<!--
编辑
出库对话框 -->
<div v-if="show
EditDialog" class="dialog-overlay" @click="closeEdit
Dialog">
<div class="dialog-content large-dialog" @click.stop>
<div class="dialog-header">
<h3>
新增
出库</h3>
<button @click="close
Add
Dialog" class="close-btn">×</button>
<h3>
编辑
出库</h3>
<button @click="close
Edit
Dialog" class="close-btn">×</button>
</div>
<div class="dialog-body">
<form @submit.prevent="handleSubmit
Add
">
<form @submit.prevent="handleSubmit
Edit
">
<!-- 出库基本信息 -->
<div class="form-section">
<h4>出库基本信息</h4>
...
...
@@ -278,33 +275,215 @@
<div class="form-item">
<label class="required">出库单编号:</label>
<input
v-model="
add
FormData.deliveryNo"
v-model="
edit
FormData.deliveryNo"
class="form-input"
placeholder="请输入出库单编号"
required
/>
</div>
<div class="form-item">
<label class="required">经销商编码:</label>
<label class="required">经销商名称:</label>
<select
v-model="editFormData.dealerName"
class="form-input"
@change="handleEditDealerChange"
required
>
<option value="">请选择经销商</option>
<option
v-for="dealer in dealerList"
:key="dealer.dealerId"
:value="dealer.dealerName"
>
{{ dealer.dealerName }}
</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">关联订单编号:</label>
<input
v-model="editFormData.orderNo"
class="form-input"
placeholder="请输入关联订单编号"
required
/>
</div>
<div class="form-item">
<label class="required">出库日期:</label>
<input
v-model="
addFormData.dealerCod
e"
v-model="
editFormData.deliveryDat
e"
class="form-input"
placeholder="请输入经销商编码"
type="datetime-local"
step="1"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label
class="required">经销商名称
:</label>
<label
>仓库编码
:</label>
<input
v-model="
addFormData.dealerNam
e"
v-model="
editFormData.warehouseCod
e"
class="form-input"
placeholder="请输入经销商名称"
placeholder="请输入仓库编码"
/>
</div>
<div class="form-item">
<label>仓库名称:</label>
<input
v-model="editFormData.warehouseName"
class="form-input"
placeholder="请输入仓库名称"
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>数据来源:</label>
<input
v-model="editFormData.dataSource"
class="form-input"
placeholder="请输入数据来源"
/>
</div>
<!-- 经销商编码字段隐藏,通过选择经销商名称自动填充 -->
<input
v-model="editFormData.dealerCode"
type="hidden"
/>
</div>
</div>
<!-- 出库明细 -->
<div class="form-section">
<h4>出库明细</h4>
<div class="order-items-header">
<button type="button" @click="addEditDeliveryItem" class="add-item-btn">+ 添加明细</button>
</div>
<div class="order-items-container">
<div v-for="(item, index) in editFormData.deliveryItems" :key="index" class="order-item">
<div class="item-row-single">
<div class="form-item">
<label class="required">产品名称:</label>
<select
v-model="item.productName"
class="form-input"
@change="handleEditProductChange(index)"
required
>
<option value="">请选择产品</option>
<option
v-for="product in productList"
:key="product.productId"
:value="product.productName"
>
{{ product.productName }}
</option>
</select>
</div>
<div class="form-item">
<label class="required">产品编码:</label>
<input
v-model="item.productCode"
class="form-input readonly"
placeholder="自动填充"
readonly
required
/>
</div>
<div class="form-item">
<label class="required">出库数量:</label>
<input
v-model.number="item.deliveryQty"
class="form-input"
type="number"
placeholder="请输入出库数量"
@input="calculateEditDeliveryItemTotal(index)"
required
/>
</div>
<div class="form-item">
<label class="required">出库单价:</label>
<input
v-model.number="item.deliveryPrice"
class="form-input"
type="number"
step="0.01"
placeholder="请输入出库单价"
@input="calculateEditDeliveryItemTotal(index)"
required
/>
</div>
<div class="form-item">
<label>出库金额:</label>
<input
v-model.number="item.deliveryAmount"
class="form-input"
type="number"
step="0.01"
readonly
/>
</div>
<button type="button" @click="removeEditDeliveryItem(index)" class="remove-item-btn">删除</button>
</div>
</div>
</div>
</div>
<div class="form-actions">
<button type="button" @click="closeEditDialog" class="cancel-btn">取消</button>
<button type="submit" class="submit-btn">确定</button>
</div>
</form>
</div>
</div>
</div>
<!-- 新增出库对话框 -->
<div v-if="showAddDialog" class="dialog-overlay" @click="closeAddDialog">
<div class="dialog-content large-dialog" @click.stop>
<div class="dialog-header">
<h3>新增出库</h3>
<button @click="closeAddDialog" class="close-btn">×</button>
</div>
<div class="dialog-body">
<form @submit.prevent="handleSubmitAdd">
<!-- 出库基本信息 -->
<div class="form-section">
<h4>出库基本信息</h4>
<div class="form-row">
<div class="form-item">
<label class="required">出库单编号:</label>
<input
v-model="addFormData.deliveryNo"
class="form-input"
placeholder="请输入出库单编号"
required
/>
</div>
<div class="form-item">
<label class="required">经销商名称:</label>
<select
v-model="addFormData.dealerName"
class="form-input"
@change="handleAddDealerChange"
required
>
<option value="">请选择经销商</option>
<option
v-for="dealer in dealerList"
:key="dealer.dealerId"
:value="dealer.dealerName"
>
{{ dealer.dealerName }}
</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">关联订单编号:</label>
<input
v-model="addFormData.orderNo"
...
...
@@ -313,8 +492,6 @@
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">出库日期:</label>
<input
...
...
@@ -325,6 +502,8 @@
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>仓库编码:</label>
<input
...
...
@@ -333,8 +512,6 @@
placeholder="请输入仓库编码"
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>仓库名称:</label>
<input
...
...
@@ -343,6 +520,8 @@
placeholder="请输入仓库名称"
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>数据来源:</label>
<input
...
...
@@ -351,6 +530,11 @@
placeholder="请输入数据来源"
/>
</div>
<!-- 经销商编码字段隐藏,通过选择经销商名称自动填充 -->
<input
v-model="addFormData.dealerCode"
type="hidden"
/>
</div>
</div>
...
...
@@ -364,20 +548,30 @@
<div v-for="(item, index) in addFormData.deliveryItems" :key="index" class="order-item">
<div class="item-row-single">
<div class="form-item">
<label class="required">产品
编码
:</label>
<
inpu
t
v-model="item.product
Cod
e"
<label class="required">产品
名称
:</label>
<
selec
t
v-model="item.product
Nam
e"
class="form-input"
placeholder="请输入产品编码
"
@change="handleAddProductChange(index)
"
required
/>
>
<option value="">请选择产品</option>
<option
v-for="product in productList"
:key="product.productId"
:value="product.productName"
>
{{ product.productName }}
</option>
</select>
</div>
<div class="form-item">
<label class="required">产品
名称
:</label>
<label class="required">产品
编码
:</label>
<input
v-model="item.productName"
class="form-input"
placeholder="请输入产品名称"
v-model="item.productCode"
class="form-input readonly"
placeholder="自动填充"
readonly
required
/>
</div>
...
...
@@ -434,12 +628,17 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQueryReq } from '../../api/delivery'
import { dealerApi, type DealerInfo } from '../../api/dealer'
import { productApi } from '../../api/product'
import type { ProductSimpleRes } from '../../api/product'
// 响应式数据
const loading = ref(false)
const deliveryList = ref<DeliveryInfo[]>([])
const showDetailDialog = ref(false)
const deliveryDetail = ref<DeliveryInfo | null>(null)
const dealerList = ref<DealerInfo[]>([])
const productList = ref<ProductSimpleRes[]>([])
// 新增出库相关数据
const showAddDialog = ref(false)
...
...
@@ -455,9 +654,23 @@ const addFormData = reactive({
deliveryItems: [] as any[]
})
// 编辑出库相关数据
const showEditDialog = ref(false)
const editFormData = reactive({
deliveryId: 0,
deliveryNo: '',
dealerCode: '',
dealerName: '',
orderNo: '',
deliveryDate: '',
warehouseCode: '',
warehouseName: '',
dataSource: '',
deliveryItems: [] as any[]
})
// 搜索参数
const searchParams = reactive({
orderNo: '',
deliveryNo: '',
dealerCode: '',
dealerName: '',
...
...
@@ -466,8 +679,6 @@ const searchParams = reactive({
dataSource: '',
deliveryStartDate: '',
deliveryEndDate: '',
startDate: '',
endDate: '',
pageNum: 1,
pageSize: 10
})
...
...
@@ -539,7 +750,16 @@ const getPageNumbers = () => {
const fetchDeliveries = async () => {
try {
loading.value = true
const params = { ...searchParams, pageNum: pagination.pageNum, pageSize: pagination.pageSize }
// 处理日期格式,将日期转换为完整的日期时间格式
const processedParams = { ...searchParams }
if (processedParams.deliveryStartDate) {
processedParams.deliveryStartDate = processedParams.deliveryStartDate + ' 00:00:00'
}
if (processedParams.deliveryEndDate) {
processedParams.deliveryEndDate = processedParams.deliveryEndDate + ' 23:59:59'
}
const params = { ...processedParams, pageNum: pagination.pageNum, pageSize: pagination.pageSize }
const response = await deliveryApi.getDeliveryList(params) as any
console.log('出库查询API响应:', response)
...
...
@@ -569,12 +789,12 @@ const handleSearch = () => {
const handleReset = () => {
Object.assign(searchParams, {
order
No: '',
delivery
No: '',
dealerCode: '',
dealerName: '',
deliveryStatus: undefined,
s
tartDate: '',
e
ndDate: ''
deliveryS
tartDate: '',
deliveryE
ndDate: ''
})
pagination.pageNum = 1
fetchDeliveries()
...
...
@@ -598,6 +818,42 @@ const handleView = async (delivery: DeliveryInfo) => {
}
}
const handleEdit = async (delivery: DeliveryInfo) => {
try {
// 调用后端接口获取完整的出库详情(包括出库明细)
const response = await deliveryApi.getDeliveryDetail(delivery.deliveryId) as any
console.log('编辑出库API响应:', response)
if (response && response.deliveryId) {
// 处理日期时间格式,将后端返回的日期时间格式转换为前端datetime-local格式
let formattedDeliveryDate = response.deliveryDate
if (formattedDeliveryDate && formattedDeliveryDate.includes(' ')) {
// 将 YYYY-MM-DD HH:MM:SS 格式转换为 YYYY-MM-DDTHH:MM:SS 格式
formattedDeliveryDate = formattedDeliveryDate.replace(' ', 'T')
}
Object.assign(editFormData, {
deliveryId: response.deliveryId,
deliveryNo: response.deliveryNo,
dealerCode: response.dealerCode,
dealerName: response.dealerName,
orderNo: response.orderNo,
deliveryDate: formattedDeliveryDate,
warehouseCode: response.warehouseCode || '',
warehouseName: response.warehouseName || '',
dataSource: response.dataSource || '',
deliveryItems: response.deliveryItems || []
})
showEditDialog.value = true
} else {
showMessage('获取出库详情失败', 'error')
}
} catch (error) {
console.error('获取出库详情失败:', error)
showMessage('获取出库详情失败, 请重试', 'error')
}
}
const handleDelete = async (delivery: DeliveryInfo) => {
if (confirm(`确定要删除出库单"${delivery.deliveryNo}"吗?`)) {
try {
...
...
@@ -637,6 +893,40 @@ const closeAddDialog = () => {
showAddDialog.value = false
}
const closeEditDialog = () => {
showEditDialog.value = false
}
// 经销商选择处理
const handleAddDealerChange = () => {
const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === addFormData.dealerName)
if (selectedDealer) {
addFormData.dealerCode = selectedDealer.dealerCode
}
}
const handleEditDealerChange = () => {
const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === editFormData.dealerName)
if (selectedDealer) {
editFormData.dealerCode = selectedDealer.dealerCode
}
}
// 产品选择处理
const handleAddProductChange = (index: number) => {
const selectedProduct = productList.value.find((product: ProductSimpleRes) => product.productName === addFormData.deliveryItems[index].productName)
if (selectedProduct) {
addFormData.deliveryItems[index].productCode = selectedProduct.productCode
}
}
const handleEditProductChange = (index: number) => {
const selectedProduct = productList.value.find((product: ProductSimpleRes) => product.productName === editFormData.deliveryItems[index].productName)
if (selectedProduct) {
editFormData.deliveryItems[index].productCode = selectedProduct.productCode
}
}
const addDeliveryItem = () => {
addFormData.deliveryItems.push({
productCode: '',
...
...
@@ -651,6 +941,20 @@ const removeDeliveryItem = (index: number) => {
addFormData.deliveryItems.splice(index, 1)
}
const addEditDeliveryItem = () => {
editFormData.deliveryItems.push({
productCode: '',
productName: '',
deliveryQty: 1,
deliveryPrice: 0,
deliveryAmount: 0
})
}
const removeEditDeliveryItem = (index: number) => {
editFormData.deliveryItems.splice(index, 1)
}
const calculateDeliveryItemTotal = (index: number) => {
const item = addFormData.deliveryItems[index]
if (item.deliveryQty && item.deliveryPrice) {
...
...
@@ -658,6 +962,13 @@ const calculateDeliveryItemTotal = (index: number) => {
}
}
const calculateEditDeliveryItemTotal = (index: number) => {
const item = editFormData.deliveryItems[index]
if (item.deliveryQty && item.deliveryPrice) {
item.deliveryAmount = item.deliveryQty * item.deliveryPrice
}
}
const handleSubmitAdd = async () => {
try {
// 处理日期时间格式,将datetime-local格式转换为后端需要的格式
...
...
@@ -680,9 +991,9 @@ const handleSubmitAdd = async () => {
deliveryItems: addFormData.deliveryItems.map(item => ({
productCode: item.productCode,
productName: item.productName,
product
Qty: item.deliveryQty,
unit
Price: item.deliveryPrice,
totalPrice
: item.deliveryAmount
delivery
Qty: item.deliveryQty,
delivery
Price: item.deliveryPrice,
deliveryAmount
: item.deliveryAmount
}))
}
...
...
@@ -696,6 +1007,44 @@ const handleSubmitAdd = async () => {
}
}
const handleSubmitEdit = async () => {
try {
// 处理日期时间格式,将datetime-local格式转换为后端需要的格式
let formattedDeliveryDate = editFormData.deliveryDate
if (formattedDeliveryDate && formattedDeliveryDate.includes('T')) {
// 将 YYYY-MM-DDTHH:MM:SS 格式转换为 YYYY-MM-DD HH:MM:SS 格式
formattedDeliveryDate = formattedDeliveryDate.replace('T', ' ')
}
// 构造编辑出库的请求数据
const deliveryData = {
deliveryId: editFormData.deliveryId,
deliveryNo: editFormData.deliveryNo,
dealerCode: editFormData.dealerCode,
dealerName: editFormData.dealerName,
orderNo: editFormData.orderNo,
deliveryDate: formattedDeliveryDate,
warehouseCode: editFormData.warehouseCode,
dataSource: editFormData.dataSource || '系统录入',
deliveryItems: editFormData.deliveryItems.map(item => ({
productCode: item.productCode,
productName: item.productName,
deliveryQty: item.deliveryQty,
deliveryPrice: item.deliveryPrice,
deliveryAmount: item.deliveryAmount
}))
}
await deliveryApi.updateDelivery(deliveryData)
showMessage('编辑出库成功', 'success')
closeEditDialog()
fetchDeliveries() // 刷新列表
} catch (error) {
console.error('编辑出库失败:', error)
showMessage('编辑出库失败, 请重试', 'error')
}
}
const handlePageChange = (page: number) => {
...
...
@@ -731,9 +1080,37 @@ const closeDetailDialog = () => {
deliveryDetail.value = null
}
// 加载经销商列表
const loadDealers = async () => {
try {
const response = await dealerApi.getAllDealers() as any
if (response && response.data && Array.isArray(response.data)) {
dealerList.value = response.data
}
} catch (error) {
console.error('加载经销商列表失败:', error)
showMessage('加载经销商列表失败', 'error')
}
}
// 加载产品列表
const loadProducts = async () => {
try {
const response = await productApi.getAllProducts() as any
if (response && response.data && Array.isArray(response.data)) {
productList.value = response.data
}
} catch (error) {
console.error('加载产品列表失败:', error)
showMessage('加载产品列表失败', 'error')
}
}
// 生命周期
onMounted(() => {
fetchDeliveries()
loadDealers()
loadProducts()
})
</script>
...
...
@@ -958,6 +1335,11 @@ onMounted(() => {
color: white;
}
.edit-btn {
background: #ffc107;
color: #333;
}
.delete-btn {
background: #dc3545;
color: white;
...
...
@@ -1256,6 +1638,23 @@ onMounted(() => {
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.2);
}
.form-select {
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
width: 100%;
box-sizing: border-box;
background-color: #fff;
cursor: pointer;
}
.form-select:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.2);
}
.form-actions {
display: flex;
justify-content: flex-end;
...
...
@@ -1337,13 +1736,16 @@ onMounted(() => {
min-width: 0;
}
/* 产品名称字段 - 占用最多空间(现在在第一位) */
.order-item .form-item:nth-child(1) {
flex: 0 0 120px;
flex: 1 1 180px !important;
min-width: 120px !important;
max-width: 200px;
}
/* 产品编码字段 - 固定宽度(现在在第二位) */
.order-item .form-item:nth-child(2) {
flex: 1 1 200px;
min-width: 150px;
flex: 0 0 140px;
}
.order-item .form-item:nth-child(3) {
...
...
frontend/src/views/invoice/index.vue
View file @
f0d473a
...
...
@@ -101,7 +101,7 @@
<th>开票日期</th>
<th>发票金额</th>
<th>开票状态</th>
<th>
上传
时间</th>
<th>
开票
时间</th>
<th>关联订单编号</th>
<th>操作</th>
</tr>
...
...
@@ -187,20 +187,20 @@
<form @submit.prevent="handleSubmit">
<div class="form-row">
<div class="form-item">
<label class="required">
订单编号
:</label>
<label class="required">
发票号码
:</label>
<input
v-model="formData.
order
No"
v-model="formData.
invoice
No"
class="form-input"
placeholder="请输入
订单编号
"
placeholder="请输入
发票号码
"
required
/>
</div>
<div class="form-item">
<label class="required">
发票号码
:</label>
<label class="required">
订单编号
:</label>
<input
v-model="formData.
invoice
No"
v-model="formData.
order
No"
class="form-input"
placeholder="请输入
发票号码
"
placeholder="请输入
订单编号
"
required
/>
</div>
...
...
@@ -215,27 +215,28 @@
/>
</div>
<div class="form-item">
<label class="required">经销商编码:</label>
<input
v-model="formData.dealerCode"
class="form-input"
placeholder="请输入经销商编码"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">经销商名称:</label>
<
inpu
t
<
selec
t
v-model="formData.dealerName"
class="form-input"
placeholder="请输入经销商名称
"
@change="handleDealerChange
"
required
/>
>
<option value="">请选择经销商</option>
<option
v-for="dealer in dealerList"
:key="dealer.dealerId"
:value="dealer.dealerName"
>
{{ dealer.dealerName }}
</option>
</select>
</div>
</div>
<div class="form-row">
<input v-model="formData.dealerCode" type="hidden"/>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">开票日期:</label>
<input
...
...
@@ -292,6 +293,23 @@
<div class="invoice-items-container">
<div v-for="(item, index) in formData.invoiceItems" :key="index" class="invoice-item">
<div class="item-row-single">
<div class="form-item" style="flex: 1 1 200px; min-width: 150px;">
<label>产品名称:</label>
<select
v-model="item.productName"
class="form-input"
@change="handleProductChange(index)"
>
<option value="">请选择产品</option>
<option
v-for="product in productList"
:key="product.productId"
:value="product.productName"
>
{{ product.productName }}
</option>
</select>
</div>
<div class="form-item" style="flex: 0 0 120px;">
<label>产品编码:</label>
<input
...
...
@@ -300,14 +318,6 @@
placeholder="产品编码"
/>
</div>
<div class="form-item" style="flex: 1 1 200px; min-width: 150px;">
<label>产品名称:</label>
<input
v-model="item.productName"
class="form-input"
placeholder="产品名称"
/>
</div>
<div class="form-item" style="flex: 0 0 80px;">
<label>数量:</label>
<input
...
...
@@ -460,8 +470,8 @@
<table class="detail-table">
<thead>
<tr>
<th>产品编码</th>
<th>产品名称</th>
<th>产品编码</th>
<th>数量</th>
<th>单价</th>
<th>金额</th>
...
...
@@ -471,8 +481,8 @@
</thead>
<tbody>
<tr v-for="item in invoiceDetail.invoiceItems" :key="item.invoiceItemId">
<td>{{ item.productCode }}</td>
<td>{{ item.productName }}</td>
<td>{{ item.productCode }}</td>
<td>{{ item.invoiceQty }}</td>
<td>{{ formatCurrency(item.unitPriceNoTax) }}</td>
<td class="amount-text">{{ formatCurrency(item.amountNoTax) }}</td>
...
...
@@ -508,6 +518,9 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { invoiceApi, type InvoiceInfo, type InvoiceQueryReq } from '../../api/invoice'
import { dealerApi, type DealerInfo } from '../../api/dealer'
import { productApi } from '../../api/product'
import type { ProductSimpleRes } from '../../api/product'
// 响应式数据
const loading = ref(false)
...
...
@@ -516,6 +529,8 @@ const showDialog = ref(false)
const showDetailDialog = ref(false)
const isEdit = ref(false)
const invoiceDetail = ref<InvoiceInfo | null>(null)
const dealerList = ref<DealerInfo[]>([])
const productList = ref<ProductSimpleRes[]>([])
// 搜索参数
const searchParams = reactive({
...
...
@@ -950,6 +965,48 @@ const handleTableViewToggle = () => {
showMessage('视图切换功能开发中...', 'warning')
}
// 加载经销商列表
const loadDealers = async () => {
try {
const response = await dealerApi.getAllDealers() as any
if (response && response.data && Array.isArray(response.data)) {
dealerList.value = response.data
}
} catch (error) {
console.error('加载经销商列表失败:', error)
showMessage('加载经销商列表失败', 'error')
}
}
// 经销商选择变化处理
const handleDealerChange = () => {
const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === formData.dealerName)
if (selectedDealer) {
formData.dealerCode = selectedDealer.dealerCode
}
}
// 加载产品列表
const loadProducts = async () => {
try {
const response = await productApi.getAllProducts() as any
if (response && response.data && Array.isArray(response.data)) {
productList.value = response.data
}
} catch (error) {
console.error('加载产品列表失败:', error)
showMessage('加载产品列表失败', 'error')
}
}
// 产品选择变化处理
const handleProductChange = (index: number) => {
const selectedProduct = productList.value.find((product: ProductSimpleRes) => product.productName === formData.invoiceItems[index].productName)
if (selectedProduct) {
formData.invoiceItems[index].productCode = selectedProduct.productCode
}
}
const closeDialog = () => {
showDialog.value = false
}
...
...
@@ -962,6 +1019,8 @@ const closeDetailDialog = () => {
// 生命周期
onMounted(() => {
fetchOrders()
loadDealers()
loadProducts()
})
</script>
...
...
frontend/src/views/order/index.vue
View file @
f0d473a
...
...
@@ -235,25 +235,25 @@
/>
</div>
<div class="form-item">
<label class="required">经销商编码:</label>
<input
v-model="formData.dealerCode"
class="form-input"
placeholder="请输入经销商编码"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">经销商名称:</label>
<
inpu
t
<
selec
t
v-model="formData.dealerName"
class="form-
inpu
t"
placeholder="请输入经销商名称
"
class="form-
selec
t"
@change="handleDealerChange
"
required
/>
>
<option value="">请选择经销商</option>
<option
v-for="dealer in dealerList"
:key="dealer.dealerId"
:value="dealer.dealerName"
>
{{ dealer.dealerName }}
</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">订单日期:</label>
<input
...
...
@@ -263,19 +263,19 @@
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label
class="required"
>订单总金额:</label>
<label>订单总金额:</label>
<input
v-model.number="formData.totalAmount"
class="form-input"
class="form-input
readonly
"
type="number"
step="0.01"
placeholder="
请输入订单总金额
"
re
quired
placeholder="
自动计算
"
re
adonly
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>返利金额:</label>
<input
...
...
@@ -286,6 +286,11 @@
placeholder="请输入返利金额"
/>
</div>
<!-- 经销商编码字段隐藏,通过选择经销商名称自动填充 -->
<input
v-model="formData.dealerCode"
type="hidden"
/>
</div>
</div>
...
...
@@ -299,21 +304,30 @@
<div v-for="(item, index) in formData.orderItems" :key="index" class="order-item">
<div class="item-row-single">
<div class="form-item">
<label class="required">产品编码
:</label>
<inpu
t
v-model="item.productCod
e"
class="form-inpu
t"
placeholder="请输入产品编码
"
<label class="required">产品名称
:</label>
<selec
t
v-model="item.productNam
e"
class="form-selec
t"
@change="handleProductChange(index)
"
required
/>
>
<option value="">请选择产品</option>
<option
v-for="product in productList"
:key="product.productId"
:value="product.productName"
>
{{ product.productName }}
</option>
</select>
</div>
<div class="form-item">
<label class="required">产品名称
:</label>
<label class="required">产品编码
:</label>
<input
v-model="item.productNam
e"
class="form-input
"
placeholder="请输入产品名称
"
required
v-model="item.productCod
e"
class="form-input readonly
"
placeholder="自动填充
"
readonly
/>
</div>
<div class="form-item">
...
...
@@ -343,9 +357,10 @@
<label>明细金额:</label>
<input
v-model.number="item.itemAmount"
class="form-input"
class="form-input
readonly
"
type="number"
step="0.01"
placeholder="自动计算"
readonly
/>
</div>
...
...
@@ -420,8 +435,8 @@
<table class="detail-table">
<thead>
<tr>
<th>产品编码</th>
<th>产品名称</th>
<th>产品编码</th>
<th>商品数量</th>
<th>单价</th>
<th>明细金额</th>
...
...
@@ -429,8 +444,8 @@
</thead>
<tbody>
<tr v-for="item in orderDetail.orderItems" :key="item.itemId">
<td>{{ item.productCode }}</td>
<td>{{ item.productName }}</td>
<td>{{ item.productCode }}</td>
<td>{{ item.productQty }}</td>
<td>{{ formatCurrency(item.unitPrice) }}</td>
<td>{{ formatCurrency(item.itemAmount) }}</td>
...
...
@@ -448,6 +463,9 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { orderApi, type OrderInfo, type OrderQueryReq, type OrderAddReq, type OrderUpdateReq, type OrderItemAddReq } from '../../api/order'
import { dealerApi, type DealerInfo } from '../../api/dealer'
import { productApi } from '../../api/product'
import type { ProductSimpleRes } from '../../api/product'
// 响应式数据
const loading = ref(false)
...
...
@@ -457,6 +475,8 @@ const showDialog = ref(false)
const showDetailDialog = ref(false)
const isEdit = ref(false)
const orderDetail = ref<OrderInfo | null>(null)
const dealerList = ref<DealerInfo[]>([])
const productList = ref<ProductSimpleRes[]>([])
// 搜索参数
const searchParams = reactive<OrderQueryReq>({
...
...
@@ -697,6 +717,15 @@ const handleEdit = async (order: OrderInfo) => {
verifyStatus: response.verifyStatus,
orderItems: response.orderItems || []
})
// 编辑时重新计算总金额,确保数据一致性
calculateTotalAmount()
// 编辑时设置经销商编码(如果经销商名称已选择)
if (formData.dealerName) {
const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === formData.dealerName)
if (selectedDealer) {
formData.dealerCode = selectedDealer.dealerCode
}
}
showDialog.value = true
} else {
showMessage('获取订单详情失败', 'error')
...
...
@@ -802,10 +831,14 @@ const addOrderItem = () => {
unitPrice: 0,
itemAmount: 0
})
// 重新计算总金额
calculateTotalAmount()
}
const removeOrderItem = (index: number) => {
formData.orderItems.splice(index, 1)
// 重新计算总金额
calculateTotalAmount()
}
const calculateItemTotal = (index: number) => {
...
...
@@ -813,6 +846,16 @@ const calculateItemTotal = (index: number) => {
if (item.productQty && item.unitPrice) {
item.itemAmount = item.productQty * item.unitPrice
}
// 重新计算订单总金额
calculateTotalAmount()
}
// 计算订单总金额
const calculateTotalAmount = () => {
const total = formData.orderItems.reduce((sum, item) => {
return sum + (item.itemAmount || 0)
}, 0)
formData.totalAmount = total
}
const handleSubmit = async () => {
...
...
@@ -874,6 +917,48 @@ const handleSubmit = async () => {
}
}
// 经销商选择处理
const handleDealerChange = () => {
const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === formData.dealerName)
if (selectedDealer) {
formData.dealerCode = selectedDealer.dealerCode
}
}
// 加载经销商列表
const loadDealers = async () => {
try {
const response = await dealerApi.getAllDealers() as any
if (response && response.data && Array.isArray(response.data)) {
dealerList.value = response.data
}
} catch (error) {
console.error('加载经销商列表失败:', error)
showMessage('加载经销商列表失败', 'error')
}
}
// 产品选择处理
const handleProductChange = (index: number) => {
const selectedProduct = productList.value.find((product: ProductSimpleRes) => product.productName === formData.orderItems[index].productName)
if (selectedProduct) {
formData.orderItems[index].productCode = selectedProduct.productCode
}
}
// 加载产品列表
const loadProducts = async () => {
try {
const response = await productApi.getAllProducts() as any
if (response && response.data && Array.isArray(response.data)) {
productList.value = response.data
}
} catch (error) {
console.error('加载产品列表失败:', error)
showMessage('加载产品列表失败', 'error')
}
}
const closeDialog = () => {
showDialog.value = false
formData.orderItems = []
...
...
@@ -887,6 +972,8 @@ const closeDetailDialog = () => {
// 生命周期
onMounted(() => {
fetchOrders()
loadDealers()
loadProducts()
})
</script>
...
...
@@ -1360,7 +1447,26 @@ onMounted(() => {
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
width: 200px;
width: 250px;
height: 32px;
box-sizing: border-box;
}
.form-select {
background-color: #fff;
cursor: pointer;
}
.form-select:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.form-input.readonly {
background-color: #f5f5f5;
color: #666;
cursor: not-allowed;
}
/* 订单明细中的输入框样式 - 优化空间利用 */
...
...
@@ -1378,30 +1484,38 @@ onMounted(() => {
min-width: 0;
}
/* 产品
编码字段
*/
/* 产品
名称字段 - 占用最多空间,增加长度(现在在第一位)
*/
.order-item .form-item:nth-child(1) {
flex: 0 0 120px;
flex: 1.5 1 200px;
min-width: 150px;
}
/* 产品
名称字段 - 占用更多空间
*/
/* 产品
编码字段 - 固定宽度(现在在第二位),往左移动
*/
.order-item .form-item:nth-child(2) {
flex:
1 1 20
0px;
m
in-width: 150
px;
flex:
0 0 18
0px;
m
argin-left: -15
px;
}
/* 数量字段 */
/* 产品编码输入框宽度调整为90% */
.order-item .form-item:nth-child(2) .form-input {
width: 90%;
}
/* 数量字段 - 较小宽度,往左移动 */
.order-item .form-item:nth-child(3) {
flex: 0 0 80px;
margin-left: -10px;
}
/* 单价字段 */
/* 单价字段
- 中等宽度,往左移动
*/
.order-item .form-item:nth-child(4) {
flex: 0 0 100px;
flex: 0 0 110px;
margin-left: -5px;
}
/* 明细金额字段 */
/* 明细金额字段
- 中等宽度
*/
.order-item .form-item:nth-child(5) {
flex: 0 0 1
0
0px;
flex: 0 0 1
1
0px;
}
.order-item .form-item label {
...
...
@@ -1431,16 +1545,19 @@ onMounted(() => {
.order-items-container {
border: 1px solid #eee;
border-radius: 4px;
padding:
6
px;
padding:
8
px;
background: #f8f9fa;
width: 100%;
}
.order-item {
background: white;
border: 1px solid #ddd;
border-radius: 4px;
padding: 6px;
margin-bottom: 6px;
padding: 8px;
margin-bottom: 8px;
width: 100%;
box-sizing: border-box;
}
.order-item:last-child {
...
...
@@ -1461,22 +1578,28 @@ onMounted(() => {
/* 单行布局 - 所有字段在一行显示,优化空间利用 */
.item-row-single {
display: flex;
gap:
4
px;
gap:
8
px;
align-items: end;
flex-wrap: nowrap;
justify-content: space-between
;
width: 100%
;
}
.remove-item-btn {
padding:
4px 8
px;
padding:
6px 12
px;
background: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 11px;
height:
28
px;
height:
32
px;
align-self: end;
transition: background-color 0.2s;
flex-shrink: 0;
}
.remove-item-btn:hover {
background: #c82333;
}
.form-actions {
...
...
Please
register
or
login
to post a comment