销售合同流程

数据合并减少请求数量,减少复杂度
master
Harry Yang 2022-12-14 17:50:41 +08:00
parent df5373799f
commit 91827db481
16 changed files with 384 additions and 187 deletions

View File

@ -1,9 +1,12 @@
package cn.palmte.work.controller.backend;
import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@ -23,6 +26,9 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.validation.Valid;
import cn.palmte.work.model.Admin;
import cn.palmte.work.model.DeptRepository;
import cn.palmte.work.model.Project;
@ -30,12 +36,16 @@ import cn.palmte.work.model.ProjectBudgetIncomeDetail;
import cn.palmte.work.model.ProjectRepository;
import cn.palmte.work.model.enums.CooperationType;
import cn.palmte.work.model.enums.Enumerable;
import cn.palmte.work.model.enums.ProcessStatus;
import cn.palmte.work.model.enums.ProjectType;
import cn.palmte.work.model.enums.SealType;
import cn.palmte.work.model.process.SaleContractProcess;
import cn.palmte.work.model.process.SealTypeArray;
import cn.palmte.work.model.process.form.SaleContractDetailForm;
import cn.palmte.work.model.process.form.SaleContractProcessForm;
import cn.palmte.work.service.ProjectBudgetService;
import cn.palmte.work.utils.InterfaceUtil;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
/**
@ -43,6 +53,7 @@ import lombok.Setter;
* @since 1.0 2022/12/8 11:03
*/
@Controller
@RequiredArgsConstructor
@RequestMapping("/process")
public class ProcessController {
static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@ -52,13 +63,7 @@ public class ProcessController {
private final ProjectBudgetService projectBudgetService;
private final JdbcTemplate jdbcTemplate;
public ProcessController(ProjectRepository projectRepository, DeptRepository deptRepository, ProjectBudgetService projectBudgetService, JdbcTemplate jdbcTemplate) {
this.projectRepository = projectRepository;
this.deptRepository = deptRepository;
this.projectBudgetService = projectBudgetService;
this.jdbcTemplate = jdbcTemplate;
}
private final EntityManager entityManager;
static class FormMetadata {
// 部门
@ -139,6 +144,8 @@ public class ProcessController {
// 合同金额
public BigDecimal contractAmount;
public List<ProjectBudgetIncomeDetail> incomeDetails;
// FIXME 垫资
// 是否垫资
public final String isPrepaid = "是";
@ -157,9 +164,10 @@ public class ProcessController {
Admin admin = InterfaceUtil.getAdmin();
// 可以在对应表数据查询 是否存在再启用
List<ProjectBudgetIncomeDetail> incomeDetails = projectBudgetService.getBudgetIncomeDetail(project);
return ProjectReturnValue.builder()
.projectId(project.getId())
.incomeDetails(incomeDetails)
.projectName(project.getName())
.projectNo(project.getProjectNo())
.applyPersonName(admin.getRealName())
@ -170,101 +178,37 @@ public class ProcessController {
}
// 销售合同流程
@Setter
static class SaleContractForm {
public Integer projectId;
// 项目编号
public String projectNo;
public LocalDate applyDate;
// 项目标题
public String title;
// 项目类型
public String projectType;
// 合作类型
public String cooperationType;
// 申请人
public String applyPersonName;
public String applyDept;
// 申请部门领导
public String applyDeptLeaderName;
// 申请人电话
public String applyPersonPhone;
// 合同编号
public String contractNo;
// 合同名称
public String contractName;
// 客户名称
public String clientName;
// 用印类型
public String[] sealType;
// 税率
public String taxRate;
// 收款条件
public String paymentTerms;
public ProcessStatus status;
}
@ResponseBody
@PostMapping
public void post(@RequestBody SaleContractForm form) {
@Transactional
public void post(@RequestBody @Valid SaleContractProcessForm form) {
System.out.println(form);
SaleContractProcess entity = new SaleContractProcess();
BeanUtils.copyProperties(form, entity, "sealTypes", "applyDate");
entity.setApplyDate(LocalDate.parse(form.getApplyDate(), formatter));
entity.setSealTypes(SealTypeArray.of(form.getSealTypes()));
}
entityManager.persist(entity);
@ResponseBody
@GetMapping("/sale-contract-details/{id}")
public List<ProjectBudgetIncomeDetail> queryProjectBudgetIncomeDetail(@PathVariable int id) {
Project project = new Project();
project.setId(id);
return projectBudgetService.getBudgetIncomeDetail(project);
}
List<SaleContractDetailForm> incomeDetails = form.getIncomeDetails();
if (!CollectionUtils.isEmpty(incomeDetails)) {
jdbcTemplate.batchUpdate("update project_budget_income_detail set expiration_date =? where id =? ",
new BatchPreparedStatementSetter() {
@Setter
public static class SaleContractDetailForm {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
SaleContractDetailForm detailForm = incomeDetails.get(i);
ps.setString(1, detailForm.getExpirationDate());
ps.setInt(2, detailForm.getId());
}
public int id;
// 5 个字符
public String expirationDate;
}
@ResponseBody
@PostMapping("/sale-contract-details")
public void saleContractDetails(@RequestBody List<SaleContractDetailForm> form) {
jdbcTemplate.batchUpdate("update project_budget_income_detail set expiration_date =? where id =? ",
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
SaleContractDetailForm detailForm = form.get(i);
ps.setString(1, detailForm.expirationDate);
ps.setInt(2, detailForm.id);
}
@Override
public int getBatchSize() {
return form.size();
}
});
@Override
public int getBatchSize() {
return incomeDetails.size();
}
});
}
}
}

View File

@ -4,12 +4,12 @@ package cn.palmte.work.model.enums;
* 12
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 1.0 2022/12/14 15:20
* @since 2.0 2022/12/14 15:20
*/
public enum CooperationType implements Enumerable<Integer> {
STRATEGIC_COOPERATION(1, "战略合作"),
NOT_STRATEGIC_COOPERATION(2, "战略合作");
NOT_STRATEGIC_COOPERATION(2, "战略合作");
private final int value;
private final String description;

View File

@ -2,6 +2,7 @@ package cn.palmte.work.model.enums;
/**
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0
*/
public interface Descriptive {
/**

View File

@ -8,6 +8,7 @@ import java.util.function.Supplier;
* Enumerable for {@link Enum}
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0
*/
public interface Enumerable<V> extends Descriptive {

View File

@ -2,7 +2,7 @@ package cn.palmte.work.model.enums;
/**
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 1.0 2022/12/13 16:12
* @since 2.0 2022/12/13 16:12
*/
public enum ProcessStatus implements Enumerable<String> {

View File

@ -4,7 +4,7 @@ package cn.palmte.work.model.enums;
*
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 1.0 2022/12/13 15:41
* @since 2.0 2022/12/13 15:41
*/
public enum ProcessTaxRate {

View File

@ -4,7 +4,7 @@ package cn.palmte.work.model.enums;
* 1 23
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 1.0 2022/12/14 15:10
* @since 2.0 2022/12/14 15:10
*/
public enum ProjectType implements Enumerable<Integer> {

View File

@ -4,7 +4,7 @@ package cn.palmte.work.model.enums;
*
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 1.0 2022/12/13 15:25
* @since 2.0 2022/12/13 15:25
*/
public enum SealType implements Enumerable<String> {

View File

@ -0,0 +1,87 @@
package cn.palmte.work.model.process;
import org.hibernate.annotations.GenericGenerator;
import java.time.LocalDate;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import cn.palmte.work.model.enums.ProcessStatus;
import lombok.Data;
/**
*
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 16:11
*/
@Data
@Entity
@Table(name = "sale_contract_process")
public class SaleContractProcess {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GenericGenerator(name = "persistenceGenerator", strategy = "increment")
private Integer id;
private Integer projectId;
// 项目编号
private String projectNo;
private LocalDate applyDate;
// 项目标题
private String projectTitle;
// 申请人
private String applyPersonName;
// 申请部门
private String applyDept;
// 申请部门领导
private String applyDeptLeaderName;
// 申请人电话
private String applyPersonPhone;
// 合同编号
private String contractNo;
// 合同名称
private String contractName;
// 客户名称
private String clientName;
// 用印类型
@Convert(converter = SealTypeArrayConverter.class)
private SealTypeArray sealTypes;
// 税率
private String taxRate;
// 收款条件
private String paymentTerms;
// 状态
@Enumerated(EnumType.STRING)
private ProcessStatus status;
// 项目类型
// @Enumerated(EnumType.STRING)
// private ProjectType projectType;
// 合作类型
// @Enumerated(EnumType.STRING)
// private CooperationType cooperationType;
}

View File

@ -0,0 +1,11 @@
package cn.palmte.work.model.process;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 17:13
*/
public interface SaleContractProcessRepository extends JpaRepository<SaleContractProcess, Integer> {
}

View File

@ -0,0 +1,58 @@
package cn.palmte.work.model.process;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.persistence.Convert;
import cn.palmte.work.model.enums.Enumerable;
import cn.palmte.work.model.enums.SealType;
/**
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 16:35
*/
//@JsonSerialize
@Convert(converter = SealTypeArrayConverter.class)
public class SealTypeArray {
public static final SealTypeArray EMPTY = new SealTypeArray();
private final List<SealType> sealTypes = new ArrayList<>();
public SealTypeArray() { }
public SealTypeArray(SealType... sealTypes) {
Collections.addAll(this.sealTypes, sealTypes);
}
public List<SealType> getSealTypes() {
return sealTypes;
}
public void setSealTypes(List<SealType> sealTypes) {
this.sealTypes.clear();
this.sealTypes.addAll(sealTypes);
}
//
public static SealTypeArray of(SealType... sealTypes) {
return new SealTypeArray(sealTypes);
}
public static SealTypeArray of(String... sealTypes) {
return of(Arrays.stream(sealTypes)
.map(type -> Enumerable.of(SealType.class, type))
.toArray(SealType[]::new));
}
public static SealTypeArray of(Collection<SealType> sealTypes) {
SealTypeArray sealTypeArray = new SealTypeArray();
sealTypeArray.sealTypes.addAll(sealTypes);
return sealTypeArray;
}
}

View File

@ -0,0 +1,44 @@
package cn.palmte.work.model.process;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.persistence.AttributeConverter;
import cn.palmte.work.model.enums.Enumerable;
import cn.palmte.work.model.enums.SealType;
/**
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 16:36
*/
public class SealTypeArrayConverter implements AttributeConverter<SealTypeArray, String> {
@Override
public String convertToDatabaseColumn(SealTypeArray attribute) {
List<SealType> sealTypes = attribute.getSealTypes();
if (sealTypes.isEmpty()) {
return null;
}
return sealTypes.stream()
.map(SealType::getValue)
.collect(Collectors.joining(","));
}
@Override
public SealTypeArray convertToEntityAttribute(String dbData) {
return Optional.ofNullable(dbData)
.filter(StringUtils::hasText)
.map(data -> data.split(","))
.map(Arrays::stream)
.map(stream -> stream.map(element -> Enumerable.of(SealType.class, element))
.toArray(SealType[]::new))
.map(SealTypeArray::of)
.orElse(SealTypeArray.EMPTY);
}
}

View File

@ -0,0 +1,17 @@
package cn.palmte.work.model.process.form;
import lombok.Data;
/**
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 17:17
*/
@Data
public class SaleContractDetailForm {
private int id;
// 5 个字符
private String expirationDate;
}

View File

@ -0,0 +1,71 @@
package cn.palmte.work.model.process.form;
import java.time.LocalDate;
import java.util.List;
import javax.validation.constraints.NotNull;
import cn.palmte.work.model.enums.ProcessStatus;
import lombok.Data;
/**
*
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 17:05
*/
@Data
public class SaleContractProcessForm {
@NotNull
private Integer projectId;
// 项目编号
private String projectNo;
private String applyDate;
// 项目标题
private String projectTitle;
// 项目类型
// private String projectType;
// 合作类型
// private String cooperationType;
// 申请人
private String applyPersonName;
private String applyDept;
// 申请部门领导
private String applyDeptLeaderName;
// 申请人电话
private String applyPersonPhone;
// 合同编号
private String contractNo;
// 合同名称
private String contractName;
// 客户名称
private String clientName;
// 用印类型
private String[] sealTypes;
// 税率
private String taxRate;
// 收款条件
private String paymentTerms;
private ProcessStatus status;
private List<SaleContractDetailForm> incomeDetails;
}

View File

@ -0,0 +1,7 @@
/**
* Model
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 16:32
*/
package cn.palmte.work.model.process;

View File

@ -160,7 +160,7 @@
<div>
<el-form-item label="用印类型" :rules="[{ required: true, message: '用印类型不能为空'}]">
<el-checkbox-group v-model="processForm.sealType">
<el-checkbox-group v-model="processForm.sealTypes">
<#list sealTypes as sealType>
<el-checkbox label="${sealType.name()}" key="key-${sealType.name()}">${sealType.description}</el-checkbox>
</#list>
@ -246,7 +246,7 @@
<#-- 销售合同清单明细 -->
<div class="am-u-sm-12 am-u-md-12" v-if="isSaleContractDetailMode">
<el-table border :data="saleContractDetails" @row-dblclick="dbclick">
<el-table border :data="incomeDetails" @row-dblclick="dbclick">
<el-table-column type="index" :index="1" label="序号" fixed></el-table-column>
<el-table-column prop="name" label="名称" fixed width="120"></el-table-column>
<el-table-column prop="type" label="类别"></el-table-column>
@ -412,7 +412,7 @@
return {
mode: "btn", // btn , newBusinessProcurementContractProcess
processForm: {
sealType: [],
sealTypes: [],
},
projectSelected: false,
applySectorOptions: [
@ -615,7 +615,8 @@
}
],
fileList: [],
saleContractDetails: [],
// 销售合同收入明细
incomeDetails: [],
}
}
@ -636,37 +637,9 @@
this.changeMode(saleContractProcess)
},
goToSaleContractDetail() {
const { id } = this.processForm
if (id) {
const { projectId } = this.processForm
if (projectId) {
this.changeMode(saleContractDetail)
const loading = this.$loading({
lock: true,
text: '销售合同清单明细',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
fetch("${base}/process/sale-contract-details/" + id)
.then(res => res.json())
.then(data => {
// 转换数据
// @formatter:off
const computeType = (type) => {
switch (type) {
case 1: return '设备'
case 2: return '工程'
case 3: return '服务'
default: return '未知'
}
}
// @formatter:on
this.saleContractDetails = data.map(detail => ({
...detail, type: computeType(detail.type)
}))
})
.catch(err => {
this.$message.error("销售合同清单明细加载失败");
})
.finally(() => loading.close())
}
else {
this.$message.warning("项目还未选择")
@ -681,6 +654,10 @@
console.log(obj)
},
initForm(form) {
this.processForm = { ...form, sealTypes: [] }
},
queryProject(q, callback) {
if (isBlank(q)) {
return
@ -704,7 +681,7 @@
handleSelectProject(selected) {
const { id, name } = selected
if (!id) {
this.processForm = {}
this.initForm({})
return
}
const loading = this.$loading({
@ -717,13 +694,24 @@
fetch("${base}/process/projects/" + id)
.then(res => res.json())
.then(data => {
this.processForm = {
sealType: [],
projectId: id,
applyDate: new Date().toLocaleDateString(),
...data
const { incomeDetails, ...form } = data
// 转换数据
// @formatter:off
const computeType = (type) => {
switch (type) {
case 1: return '设备'
case 2: return '工程'
case 3: return '服务'
default: return '未知'
}
}
// @formatter:on
this.initForm(form)
this.projectSelected = true
this.incomeDetails = incomeDetails.map(detail => ({
...detail, type: computeType(detail.type)
}))
})
.catch(err => {
this.$message.error("项目'" + name + "'加载失败");
@ -732,9 +720,8 @@
},
clearProjectProcess() {
this.projectSelected = false
this.processForm = {
sealType: []
}
this.initForm({})
this.incomeDetails = []
},
saveDraft() {
@ -752,14 +739,20 @@
background: 'rgba(0, 0, 0, 0.7)'
})
const processForm = this.processForm
console.log(processForm)
const form = {
...this.processForm,
projectTitle: this.projectTitle,
incomeDetails: this.incomeDetails.map(detail => ({
id: detail.id, expirationDate: detail.expirationDate
}))
}
fetch("${base}/process", {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(processForm),
body: JSON.stringify(form),
}).then(response => {
this.$message({
showClose: true,
@ -777,32 +770,7 @@
},
submitToSaleContractProcess() {
const loading = this.$loading({
lock: true,
text: '正在提交',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
const form = this.saleContractDetails.map(detail => ({
id: detail.id, expirationDate: detail.expirationDate
}))
fetch("${base}/process/sale-contract-details", {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(form),
}).then(response => {
this.$message({
showClose: true,
message: '提交成功',
type: 'success'
})
this.goToSaleContractProcess()
}).catch(err => {
this.$message.error("项目提交失败");
}).finally(() => loading.close())
this.goToSaleContractProcess()
},
handleRemove(file, fileList) {
@ -863,19 +831,7 @@
methods,
mounted() {
fetch("${base}/process/projects/135")
.then(res => res.json())
.then(data => {
this.processForm = {
sealType: [],
projectId: 135,
...data
}
this.projectSelected = true
})
.catch(err => {
this.$message.error("测试项目'加载失败");
})
this.handleSelectProject({ id: 135, name: '' })
},
})