增加客户

This commit is contained in:
Mrking 2024-08-11 21:56:57 +08:00
parent 877b451f4b
commit e99bf68869
10 changed files with 268 additions and 85 deletions

View File

@ -1,5 +1,10 @@
package cn.hangtag.module.oms.controller.admin.customer;
import cn.hangtag.framework.common.util.collection.MapUtils;
import cn.hangtag.framework.ip.core.utils.AreaUtils;
import cn.hangtag.module.system.api.dept.dto.DeptRespDTO;
import cn.hangtag.module.system.api.user.dto.AdminUserRespDTO;
import cn.hutool.core.collection.CollUtil;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
@ -13,6 +18,7 @@ import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import java.util.stream.Stream;
import cn.hangtag.framework.common.pojo.PageParam;
import cn.hangtag.framework.common.pojo.PageResult;
@ -24,6 +30,7 @@ import cn.hangtag.framework.excel.core.util.ExcelUtils;
import cn.hangtag.framework.apilog.core.annotation.ApiAccessLog;
import static cn.hangtag.framework.apilog.core.enums.OperateTypeEnum.*;
import static java.util.Collections.singletonList;
import cn.hangtag.module.oms.controller.admin.customer.vo.*;
import cn.hangtag.module.oms.dal.dataobject.customer.CustomerDO;
@ -39,6 +46,8 @@ public class CustomerController {
@Resource
private CustomerService customerService;
@PostMapping("/create")
@Operation(summary = "创建客户")
@PreAuthorize("@ss.hasPermission('oms:customer:create')")
@ -72,6 +81,24 @@ public class CustomerController {
return success(BeanUtils.toBean(customer, CustomerRespVO.class));
}
private CustomerRespVO buildClueDetail(CustomerDO clue) {
if (clue == null) {
return null;
}
return buildDetailList(singletonList(clue)).get(0);
}
private List<CustomerRespVO> buildDetailList(List<CustomerDO> list) {
if (CollUtil.isEmpty(list)) {
return Collections.emptyList();
}
// 2. 转换成 VO
return BeanUtils.toBean(list, CustomerRespVO.class, customerVO -> {
customerVO.setAreaName(AreaUtils.format(customerVO.getAreaId()));
});
}
@GetMapping("/page")
@Operation(summary = "获得客户分页")
@PreAuthorize("@ss.hasPermission('oms:customer:query')")
@ -103,4 +130,8 @@ public class CustomerController {
return success(customerService.getCustomerAddressListByCustomerId(customerId));
}
}

View File

@ -34,9 +34,13 @@ public class CustomerRespVO {
@ExcelProperty("联系人手机号")
private String phone;
@Schema(description = "所属地区")
@Schema(description = "所属地区", example = "1024")
@ExcelProperty("所属地区")
private String district;
private Integer areaId;
@Schema(description = "地区名称", example = "北京市")
@ExcelProperty("地区名称")
private String areaName;
@Schema(description = "类型", example = "2")
@ExcelProperty("类型")

View File

@ -28,7 +28,7 @@ public class CustomerSaveReqVO {
private String phone;
@Schema(description = "所属地区")
private String district;
private Integer areaId;
@Schema(description = "数据状态", example = "2")
private String status;

View File

@ -39,18 +39,10 @@ public class CustomerAddressDO extends BaseDO {
* 联系电话
*/
private String phone;
/**
* 省份
*/
private String province;
/**
* 城市
*/
private String city;
/**
* /
*/
private String district;
private Integer areaId;
/**
* 详细地址
*/

View File

@ -50,7 +50,7 @@ public class CustomerDO extends BaseDO {
/**
* 所属地区
*/
private String district;
private Integer areaId;
/**
* 类型
*/

View File

@ -0,0 +1,142 @@
package cn.hangtag.module.oms.service.customer;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import javax.annotation.Resource;
import cn.hangtag.framework.test.core.ut.BaseDbUnitTest;
import cn.hangtag.module.oms.controller.admin.customer.vo.*;
import cn.hangtag.module.oms.dal.dataobject.customer.CustomerDO;
import cn.hangtag.module.oms.dal.mysql.customer.CustomerMapper;
import cn.hangtag.framework.common.pojo.PageResult;
import javax.annotation.Resource;
import org.springframework.context.annotation.Import;
import java.util.*;
import java.time.LocalDateTime;
import static cn.hutool.core.util.RandomUtil.*;
import static cn.hangtag.module.oms.enums.ErrorCodeConstants.*;
import static cn.hangtag.framework.test.core.util.AssertUtils.*;
import static cn.hangtag.framework.test.core.util.RandomUtils.*;
import static cn.hangtag.framework.common.util.date.LocalDateTimeUtils.*;
import static cn.hangtag.framework.common.util.object.ObjectUtils.*;
import static cn.hangtag.framework.common.util.date.DateUtils.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* {@link CustomerServiceImpl} 的单元测试类
*
* @author wwb
*/
@Import(CustomerServiceImpl.class)
public class CustomerServiceImplTest extends BaseDbUnitTest {
@Resource
private CustomerServiceImpl customerService;
@Resource
private CustomerMapper customerMapper;
@Test
public void testCreateCustomer_success() {
// 准备参数
CustomerSaveReqVO createReqVO = randomPojo(CustomerSaveReqVO.class).setId(null);
// 调用
Long customerId = customerService.createCustomer(createReqVO);
// 断言
assertNotNull(customerId);
// 校验记录的属性是否正确
CustomerDO customer = customerMapper.selectById(customerId);
assertPojoEquals(createReqVO, customer, "id");
}
@Test
public void testUpdateCustomer_success() {
// mock 数据
CustomerDO dbCustomer = randomPojo(CustomerDO.class);
customerMapper.insert(dbCustomer);// @Sql: 先插入出一条存在的数据
// 准备参数
CustomerSaveReqVO updateReqVO = randomPojo(CustomerSaveReqVO.class, o -> {
o.setId(dbCustomer.getId()); // 设置更新的 ID
});
// 调用
customerService.updateCustomer(updateReqVO);
// 校验是否更新正确
CustomerDO customer = customerMapper.selectById(updateReqVO.getId()); // 获取最新的
assertPojoEquals(updateReqVO, customer);
}
@Test
public void testUpdateCustomer_notExists() {
// 准备参数
CustomerSaveReqVO updateReqVO = randomPojo(CustomerSaveReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> customerService.updateCustomer(updateReqVO), CUSTOMER_NOT_EXISTS);
}
@Test
public void testDeleteCustomer_success() {
// mock 数据
CustomerDO dbCustomer = randomPojo(CustomerDO.class);
customerMapper.insert(dbCustomer);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbCustomer.getId();
// 调用
customerService.deleteCustomer(id);
// 校验数据不存在了
assertNull(customerMapper.selectById(id));
}
@Test
public void testDeleteCustomer_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> customerService.deleteCustomer(id), CUSTOMER_NOT_EXISTS);
}
@Test
@Disabled // TODO 请修改 null 为需要的值然后删除 @Disabled 注解
public void testGetCustomerPage() {
// mock 数据
CustomerDO dbCustomer = randomPojo(CustomerDO.class, o -> { // 等会查询到
o.setId(null);
o.setName(null);
o.setType(null);
o.setStatus(null);
});
customerMapper.insert(dbCustomer);
// 测试 id 不匹配
customerMapper.insert(cloneIgnoreId(dbCustomer, o -> o.setId(null)));
// 测试 name 不匹配
customerMapper.insert(cloneIgnoreId(dbCustomer, o -> o.setName(null)));
// 测试 type 不匹配
customerMapper.insert(cloneIgnoreId(dbCustomer, o -> o.setType(null)));
// 测试 status 不匹配
customerMapper.insert(cloneIgnoreId(dbCustomer, o -> o.setStatus(null)));
// 准备参数
CustomerPageReqVO reqVO = new CustomerPageReqVO();
reqVO.setId(null);
reqVO.setName(null);
reqVO.setType(null);
reqVO.setStatus(null);
// 调用
PageResult<CustomerDO> pageResult = customerService.getCustomerPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbCustomer, pageResult.getList().get(0));
}
}

View File

@ -1,52 +1,53 @@
import request from '@/config/axios'
// 客户 VO
export interface CustomerVO {
name: string // 名称
email: string // 邮箱
contacts: string // 联系人
phone: string // 联系人手机号
district: string // 所属地区
type: string // 类型
remarks: string // 备注
}
// 客户 API
export const CustomerApi = {
// 查询客户分页
getCustomerPage: async (params: any) => {
return await request.get({ url: `/oms/customer/page`, params })
},
// 查询客户详情
getCustomer: async (id: number) => {
return await request.get({ url: `/oms/customer/get?id=` + id })
},
// 新增客户
createCustomer: async (data: CustomerVO) => {
return await request.post({ url: `/oms/customer/create`, data })
},
// 修改客户
updateCustomer: async (data: CustomerVO) => {
return await request.put({ url: `/oms/customer/update`, data })
},
// 删除客户
deleteCustomer: async (id: number) => {
return await request.delete({ url: `/oms/customer/delete?id=` + id })
},
// 导出客户 Excel
exportCustomer: async (params) => {
return await request.download({ url: `/oms/customer/export-excel`, params })
},
// ==================== 子表(用户地址) ====================
// 获得用户地址列表
getCustomerAddressListByCustomerId: async (customerId) => {
return await request.get({ url: `/oms/customer/customer-address/list-by-customer-id?customerId=` + customerId })
},
}
import request from '@/config/axios'
// 客户 VO
export interface CustomerVO {
name: string // 名称
email: string // 邮箱
contacts: string // 联系人
phone: string // 联系人手机号
areaId: number // 所在地
areaName?: string // 所在地名称
type: string // 类型
remarks: string // 备注
}
// 客户 API
export const CustomerApi = {
// 查询客户分页
getCustomerPage: async (params: any) => {
return await request.get({ url: `/oms/customer/page`, params })
},
// 查询客户详情
getCustomer: async (id: number) => {
return await request.get({ url: `/oms/customer/get?id=` + id })
},
// 新增客户
createCustomer: async (data: CustomerVO) => {
return await request.post({ url: `/oms/customer/create`, data })
},
// 修改客户
updateCustomer: async (data: CustomerVO) => {
return await request.put({ url: `/oms/customer/update`, data })
},
// 删除客户
deleteCustomer: async (id: number) => {
return await request.delete({ url: `/oms/customer/delete?id=` + id })
},
// 导出客户 Excel
exportCustomer: async (params) => {
return await request.download({ url: `/oms/customer/export-excel`, params })
},
// ==================== 子表(用户地址) ====================
// 获得用户地址列表
getCustomerAddressListByCustomerId: async (customerId) => {
return await request.get({ url: `/oms/customer/customer-address/list-by-customer-id?customerId=` + customerId })
},
}

View File

@ -19,9 +19,9 @@
<el-form-item label="联系人手机号" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入联系人手机号" />
</el-form-item>
<el-form-item label="所属地区" prop="district">
<el-form-item label="所属地区" prop="areaId">
<el-cascader
v-model="formData.district"
v-model="formData.areaId"
:options="areaList"
:props="defaultProps"
class="w-1/1"
@ -81,7 +81,7 @@ const formData = ref({
email: undefined,
contacts: undefined,
phone: undefined,
district: undefined,
areaId: undefined,
status: undefined,
type: undefined,
remarks: undefined,
@ -171,7 +171,7 @@ const resetForm = () => {
email: undefined,
contacts: undefined,
phone: undefined,
district: undefined,
areaId: undefined,
status: '1',
type: undefined,
remarks: undefined,

View File

@ -23,14 +23,23 @@
</el-form-item>
</template>
</el-table-column>
<el-table-column label="省份" min-width="150">
<el-table-column label="请选择地区" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.province`" :rules="formRules.province" class="mb-0px!">
<el-input v-model="row.province" placeholder="请输入省份" />
<el-form-item :prop="`${$index}.areaId`" :rules="formRules.areaId" class="mb-0px!">
<el-cascader
v-model="row.areaId"
:options="areaList"
:props="defaultProps"
class="w-1/1"
clearable
filterable
placeholder="请选择地区"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="城市" min-width="150">
<!-- <el-table-column label="城市" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.city`" :rules="formRules.city" class="mb-0px!">
<el-input v-model="row.city" placeholder="请输入城市" />
@ -39,15 +48,15 @@
</el-table-column>
<el-table-column label="区/县" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.district`" :rules="formRules.district" class="mb-0px!">
<el-input v-model="row.district" placeholder="请输入区/县" />
<el-form-item :prop="`${$index}.areaId`" :rules="formRules.areaId" class="mb-0px!">
<el-input v-model="row.areaId" placeholder="请输入区/县" />
</el-form-item>
</template>
</el-table-column>
</el-table-column>-->
<el-table-column label="详细地址" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.address`" :rules="formRules.address" class="mb-0px!">
<el-input v-model="row.address" placeholder="请输入详细地址" />
<el-input v-model="row.address" placeholder="请输入详细地址" autocomplete="off"/>
</el-form-item>
</template>
</el-table-column>
@ -74,6 +83,8 @@
</template>
<script setup lang="ts">
import { CustomerApi } from '@/api/oms/customer'
import {defaultProps} from "@/utils/tree";
import * as AreaApi from "@/api/system/area";
const props = defineProps<{
customerId: undefined // ID
@ -84,14 +95,13 @@ const formRules = reactive({
customerId: [{ required: true, message: '关联到客户的ID外键指向客户表不能为空', trigger: 'blur' }],
name: [{ required: true, message: '收货人姓名不能为空', trigger: 'blur' }],
phone: [{ required: true, message: '联系电话不能为空', trigger: 'blur' }],
province: [{ required: true, message: '省份不能为空', trigger: 'blur' }],
city: [{ required: true, message: '城市不能为空', trigger: 'blur' }],
district: [{ required: true, message: '区/县不能为空', trigger: 'blur' }],
areaId: [{ required: true, message: '地区不能为空', trigger: 'blur' }],
address: [{ required: true, message: '详细地址不能为空', trigger: 'blur' }],
isDefault: [{ required: true, message: '默认地址不能为空', trigger: 'blur' }],
})
const formRef = ref() // Ref
const changeFlag = ref(true) // change
const areaList = ref([]) //
/** 监听主表的关联字段的变化,加载对应的子表数据 */
watch(
@ -120,9 +130,7 @@ const handleAdd = () => {
customerId: undefined,
name: undefined,
phone: undefined,
province: undefined,
city: undefined,
district: undefined,
areaId: undefined,
address: undefined,
isDefault: [],
}
@ -146,7 +154,7 @@ const getData = () => {
return formData.value
}
defineExpose({ validate, getData })
defineExpose({ validate, getData})
const handleSwitchChange = async (index: number, value?: string) => {
@ -163,5 +171,10 @@ const handleSwitchChange = async (index: number, value?: string) => {
formData.value[index].isDefault = true
}
}
/** 初始化 **/
onMounted(async() => {
//
areaList.value = await AreaApi.getAreaTree()
})
</script>

View File

@ -104,7 +104,7 @@
<el-table-column label="邮箱" align="center" prop="email" />
<el-table-column label="联系人" align="center" prop="contacts" />
<el-table-column label="联系人手机号" align="center" prop="phone" />
<el-table-column label="所属地区" align="center" prop="district" />
<el-table-column label="所属地区" align="center" prop="areaId" />
<el-table-column label="类型" align="center" prop="type" />
<el-table-column label="数据状态" align="center" prop="status">
<template #default="scope">