zhouhui.jiang

update 订单、出库 、发票

......@@ -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)
......
......@@ -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')")
......
......@@ -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());
}
}
}
......
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;
}
......@@ -69,4 +69,10 @@ public interface DealerInfoService extends IService<DealerInfo> {
* @return 是否成功
*/
boolean updateDealerAuditStatus(Long dealerId, Integer qualificationAuditStatus, String auditOpinion);
/**
* 获取所有启用的经销商
* @return 经销商列表
*/
List<DealerInfo> getAllDealers();
}
......
......@@ -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();
}
......
......@@ -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);
}
}
......
......@@ -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());
}
}
......
......@@ -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:
......
......@@ -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']
}
}
......
......@@ -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')
}
}
......
......@@ -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')
}
}
......
This diff is collapsed. Click to expand it.
......@@ -5,9 +5,9 @@
<div class="search-row">
<div class="search-item">
<label>发票编号:</label>
<input
v-model="searchParams.invoiceNo"
class="search-input"
<input
v-model="searchParams.invoiceNo"
class="search-input"
placeholder="请输入发票编号"
/>
</div>
......@@ -48,7 +48,7 @@
<div class="search-item">
<label>开票开始日期:</label>
<input
v-model="searchParams.invoiceStartDate"
v-model="searchParams.invoiceStartDate"
class="search-input"
type="date"
/>
......@@ -56,7 +56,7 @@
<div class="search-item">
<label>开票结束日期:</label>
<input
v-model="searchParams.invoiceEndDate"
v-model="searchParams.invoiceEndDate"
class="search-input"
type="date"
/>
......@@ -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.orderNo"
v-model="formData.invoiceNo"
class="form-input"
placeholder="请输入订单编号"
placeholder="请输入发票号码"
required
/>
</div>
<div class="form-item">
<label class="required">发票号码:</label>
<label class="required">订单编号:</label>
<input
v-model="formData.invoiceNo"
v-model="formData.orderNo"
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>
<input
<select
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>
......
This diff is collapsed. Click to expand it.