增加pdf打印

This commit is contained in:
Mrking 2024-09-15 20:04:47 +08:00
parent 20f13d7f5b
commit c39aa21b7f
62 changed files with 3233 additions and 350 deletions

View File

@ -74,7 +74,7 @@ public class TenantSecurityWebFilter extends ApiRequestFilter {
}
// 如果非允许忽略租户的 URL则校验租户是否合法
if (!isIgnoreUrl(request)) {
if (false) {
// 2. 如果请求未带租户的编号不允许访问
if (tenantId == null) {
log.error("[doFilterInternal][URL({}/{}) 未传递租户编号]", request.getRequestURI(), request.getMethod());

View File

@ -11,8 +11,7 @@
"name": "hangtag.tenant.enable",
"type": "java.lang.Boolean",
"description": "是否开启",
"sourceType": "cn.hangtag.framework.tenant.config.TenantProperties",
"defaultValue": true
"sourceType": "cn.hangtag.framework.tenant.config.TenantProperties"
},
{
"name": "hangtag.tenant.ignore-tables",

View File

@ -11,8 +11,7 @@
"name": "hangtag.cache.redis-scan-batch-size",
"type": "java.lang.Integer",
"description": "redis scan 一次返回数量",
"sourceType": "cn.hangtag.framework.redis.config.HangtagCacheProperties",
"defaultValue": 30
"sourceType": "cn.hangtag.framework.redis.config.HangtagCacheProperties"
}
],
"hints": []

View File

@ -11,22 +11,19 @@
"name": "hangtag.security.mock-enable",
"type": "java.lang.Boolean",
"description": "mock 模式的开关",
"sourceType": "cn.hangtag.framework.security.config.SecurityProperties",
"defaultValue": false
"sourceType": "cn.hangtag.framework.security.config.SecurityProperties"
},
{
"name": "hangtag.security.mock-secret",
"type": "java.lang.String",
"description": "mock 模式的密钥 一定要配置密钥,保证安全性",
"sourceType": "cn.hangtag.framework.security.config.SecurityProperties",
"defaultValue": "test"
"sourceType": "cn.hangtag.framework.security.config.SecurityProperties"
},
{
"name": "hangtag.security.password-encoder-length",
"type": "java.lang.Integer",
"description": "PasswordEncoder 加密复杂度,越高开销越大",
"sourceType": "cn.hangtag.framework.security.config.SecurityProperties",
"defaultValue": 4
"sourceType": "cn.hangtag.framework.security.config.SecurityProperties"
},
{
"name": "hangtag.security.permit-all-urls",
@ -38,15 +35,13 @@
"name": "hangtag.security.token-header",
"type": "java.lang.String",
"description": "HTTP 请求时,访问令牌的请求 Header",
"sourceType": "cn.hangtag.framework.security.config.SecurityProperties",
"defaultValue": "Authorization"
"sourceType": "cn.hangtag.framework.security.config.SecurityProperties"
},
{
"name": "hangtag.security.token-parameter",
"type": "java.lang.String",
"description": "HTTP 请求时,访问令牌的请求参数 初始目的:解决 WebSocket 无法通过 header 传参,只能通过 token 参数拼接",
"sourceType": "cn.hangtag.framework.security.config.SecurityProperties",
"defaultValue": "token"
"sourceType": "cn.hangtag.framework.security.config.SecurityProperties"
}
],
"hints": []

View File

@ -305,11 +305,11 @@ public class GlobalExceptionHandler {
"[微信公众号 hangtag-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]");
}
// 4. 商城系统
if (StrUtil.containsAny(message, "product_", "promotion_", "trade_")) {
/* if (StrUtil.containsAny(message, "product_", "promotion_", "trade_")) {
log.error("[商城系统 hangtag-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[商城系统 hangtag-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
}
}*/
// 5. ERP 系统
if (message.contains("erp_")) {
log.error("[ERP 系统 hangtag-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]");

View File

@ -114,8 +114,7 @@
"name": "hangtag.xss.enable",
"type": "java.lang.Boolean",
"description": "是否开启,默认为 true",
"sourceType": "cn.hangtag.framework.xss.config.XssProperties",
"defaultValue": true
"sourceType": "cn.hangtag.framework.xss.config.XssProperties"
},
{
"name": "hangtag.xss.exclude-urls",

View File

@ -11,15 +11,13 @@
"name": "hangtag.websocket.path",
"type": "java.lang.String",
"description": "WebSocket 的连接路径",
"sourceType": "cn.hangtag.framework.websocket.config.WebSocketProperties",
"defaultValue": "\/ws"
"sourceType": "cn.hangtag.framework.websocket.config.WebSocketProperties"
},
{
"name": "hangtag.websocket.sender-type",
"type": "java.lang.String",
"description": "消息发送器的类型 可选值local、redis、rocketmq、kafka、rabbitmq",
"sourceType": "cn.hangtag.framework.websocket.config.WebSocketProperties",
"defaultValue": "local"
"sourceType": "cn.hangtag.framework.websocket.config.WebSocketProperties"
}
],
"hints": []

View File

@ -16,5 +16,6 @@ public interface ErrorCodeConstants extends cn.hangtag.module.system.enums.Erro
ErrorCode PRODUCE_ORDER_NOT_EXISTS = new ErrorCode(4000, "生产制单不存在");
ErrorCode PRODUCE_ORDER_EXISTS = new ErrorCode(4002, "生产制单已经存在");
ErrorCode PRODUCE_ORDER_IMPORT_LIST_IS_EMPTY = new ErrorCode(4003, "导入生产制单数据不能为空");
ErrorCode SALE_CONTRACT_NOT_EXISTS = new ErrorCode(5000, "OMS销售合约不存在");
}

View File

@ -73,6 +73,12 @@
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
<!-- HTML转PDF工具flying-saucer -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
@ -90,6 +96,12 @@
<artifactId>openpdf</artifactId>
<version>1.3.14</version>
</dependency>
<dependency>
<groupId>org.dromara.x-easypdf</groupId>
<artifactId>x-easypdf</artifactId>
<version>3.1.1</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,28 @@
package cn.hangtag.module.oms.common.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class HtmlToPdfInterceptor extends Thread {
private InputStream is;
public HtmlToPdfInterceptor(InputStream is) {
this.is = is;
}
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
// System.out.println(line.toString()); // 输出内容
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,90 @@
package cn.hangtag.module.oms.common.utils;
import java.math.BigDecimal;
public class NumberChineseFormatterUtils {
private static final String[] CHINESE_NUMBERS = {"", "", "", "", "", "", "", "", "", ""};
private static final String[] UNITS = {"", "", "", ""};
private static final String[] BIG_UNITS = {"", "", "亿", "", "", "", "", "", "", "", "", "", ""}; // 根据需要可以增加更多大单位
public static String convertToChinese(BigDecimal price) {
if (price == null || price.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("价格不能为空或小于0");
}
// 转换为字符串并截取小数点前后部分
String priceStr = price.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString();
String integerPart = priceStr.split("\\.")[0]; // 整数部分
String decimalPart = priceStr.split("\\.")[1]; // 小数部分
// 处理整数部分
String integerChinese = convertToChinesePart(integerPart, UNITS, BIG_UNITS);
// 处理小数部分
String decimalChinese = "";
if (!"00".equals(decimalPart)) {
decimalChinese = convertToDecimalChinese(decimalPart);
}
// 合并结果
return integerChinese + "" + decimalChinese;
}
private static String convertToChinesePart(String numberStr, String[] units, String[] bigUnits) {
if ("0".equals(numberStr)) {
return CHINESE_NUMBERS[0];
}
StringBuilder sb = new StringBuilder();
int unitIndex = 0;
// int zeroCount = 0;
for (int i = numberStr.length() - 1; i >= 0; i--) {
int digit = numberStr.charAt(i) - '0';
String chineseDigit = CHINESE_NUMBERS[digit];
if (digit == 0) {
// zeroCount++;
// 连续零的处理只在非零数字后面单位变化处或字符串开始处添加一个零
if (sb.length() > 0 && (sb.charAt(sb.length() - 1) != CHINESE_NUMBERS[0].charAt(0) || unitIndex == 0 || i == 0)) {
sb.insert(0, chineseDigit);
}
} else {
// 添加非零数字和对应单位
sb.insert(0, chineseDigit + units[unitIndex]);
// zeroCount = 0; // 重置连续零的计数
}
// 切换到下一个单位
if (++unitIndex == units.length) {
unitIndex = 0; // 循环使用单位数组
if (sb.length() > 0 && i > 0) {
sb.insert(0, bigUnits[(numberStr.length()-i) / units.length]);
}
}
}
// 去除末尾可能多余的零
while (sb.length() > 0 && sb.charAt(0) == CHINESE_NUMBERS[0].charAt(0)) {
sb.deleteCharAt(0);
}
return sb.toString();
}
private static String convertToDecimalChinese(String decimalPart) {
StringBuilder sb = new StringBuilder();
if (decimalPart.charAt(0) != '0') {
sb.append(CHINESE_NUMBERS[decimalPart.charAt(0) - '0']).append("");
}
if (decimalPart.length() > 1 && decimalPart.charAt(1) != '0') {
sb.append(CHINESE_NUMBERS[decimalPart.charAt(1) - '0']).append("");
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(convertToChinese(new BigDecimal("100.00"))); // 壹佰元
}
}

View File

@ -0,0 +1,78 @@
package cn.hangtag.module.oms.common.utils;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
@Slf4j
public class WKHtmlToPdfUtil {
// wkhtmltopdf在系统中的路径
private static final String winExePath = "D:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe";
private static final String lunixExePath = "wkhtmltopdf";
/**
* html转pdf
*
* @param srcPath html路径可以是硬盘上的路径也可以是网络路径
* @param destPath pdf保存路径
* @return 转换成功返回true
*/
public static boolean convert(String srcPath, String destPath) {
File file = new File(destPath);
File parent = file.getParentFile();
// 如果pdf保存路径不存在则创建路径
if (!parent.exists()) {
parent.mkdirs();
}
StringBuilder cmd = new StringBuilder();
// wkhtmltopdf的路径
if (System.getProperty("os.name").contains("Mac")) {
cmd.append("");
} else if(System.getProperty("os.name").contains("Windows")) {
cmd.append(winExePath);
} else {
cmd.append(lunixExePath);
}
cmd.append(" -L 5mm -R 5mm");
cmd.append(" --no-stop-slow-scripts --load-error-handling ignore");
cmd.append(" --enable-local-file-access");
// cmd.append(StrUtil.format(" --header-right {} --header-line --header-spacing 3", ""));
// cmd.append(StrUtil.format(" --header-right {} --header-spacing 3", ""));
cmd.append(" ");
cmd.append("--enable-local-file-access");
cmd.append(" ");
cmd.append("--disable-smart-shrinking ");
cmd.append(" \"");
cmd.append(srcPath);
cmd.append("\" ");
cmd.append(" ");
cmd.append(destPath);
System.out.println(cmd.toString());
boolean result = true;
Process proc;
try {
if (!System.getProperty("os.name").contains("Windows")) {
// 非windows 系统
proc = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd.toString()});
} else {
proc = Runtime.getRuntime().exec(cmd.toString());
}
HtmlToPdfInterceptor error = new HtmlToPdfInterceptor(proc.getErrorStream());
HtmlToPdfInterceptor output = new HtmlToPdfInterceptor(proc.getInputStream());
error.start();
output.start();
proc.waitFor();
} catch (Exception e) {
log.error("Convert to pdf error", e);
result = false;
}
return result;
}
}

View File

@ -0,0 +1,20 @@
package cn.hangtag.module.oms.controller.admin.common.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - 数据对照 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DataComparisonRespVO<T> {
@Schema(description = "当前数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private T value;
@Schema(description = "参照数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private T reference;
}

View File

@ -26,6 +26,10 @@ public class CustomerRespVO {
@ExcelProperty("公司")
private String company;
@Schema(description = "公司地址")
@ExcelProperty("公司地址")
private String companyAddress;
@Schema(description = "邮箱")
@ExcelProperty("邮箱")
private String email;

View File

@ -21,6 +21,9 @@ public class CustomerSaveReqVO {
@Schema(description = "公司")
private String company;
@Schema(description = "公司地址")
private String companyAddress;
@Schema(description = "邮箱")
private String email;
@ -48,7 +51,7 @@ public class CustomerSaveReqVO {
@Schema(description = "备注")
private String remarks;
@Schema(description = "用户地址列表")
@Schema(description = "订单地址列表")
private List<CustomerAddressDO> customerAddresss;
}

View File

@ -0,0 +1,106 @@
package cn.hangtag.module.oms.controller.admin.salecontract;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import javax.validation.constraints.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.hangtag.framework.common.pojo.PageParam;
import cn.hangtag.framework.common.pojo.PageResult;
import cn.hangtag.framework.common.pojo.CommonResult;
import cn.hangtag.framework.common.util.object.BeanUtils;
import static cn.hangtag.framework.common.pojo.CommonResult.success;
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 cn.hangtag.module.oms.controller.admin.salecontract.vo.*;
import cn.hangtag.module.oms.dal.dataobject.salecontract.SaleContractDO;
import cn.hangtag.module.oms.dal.dataobject.salecontractentry.SaleContractEntryDO;
import cn.hangtag.module.oms.service.salecontract.SaleContractService;
@Tag(name = "管理后台 - OMS销售合约")
@RestController
@RequestMapping("/oms/sale-contract")
@Validated
public class SaleContractController {
@Resource
private SaleContractService saleContractService;
@PostMapping("/create")
@Operation(summary = "创建OMS销售合约")
@PreAuthorize("@ss.hasPermission('oms:sale-contract:create')")
public CommonResult<Long> createSaleContract(@Valid @RequestBody SaleContractSaveReqVO createReqVO) {
return success(saleContractService.createSaleContract(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新OMS销售合约")
@PreAuthorize("@ss.hasPermission('oms:sale-contract:update')")
public CommonResult<Boolean> updateSaleContract(@Valid @RequestBody SaleContractSaveReqVO updateReqVO) {
saleContractService.updateSaleContract(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除OMS销售合约")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('oms:sale-contract:delete')")
public CommonResult<Boolean> deleteSaleContract(@RequestParam("id") Long id) {
saleContractService.deleteSaleContract(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得OMS销售合约")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('oms:sale-contract:query')")
public CommonResult<SaleContractRespVO> getSaleContract(@RequestParam("id") Long id) {
SaleContractDO saleContract = saleContractService.getSaleContract(id);
return success(BeanUtils.toBean(saleContract, SaleContractRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得OMS销售合约分页")
@PreAuthorize("@ss.hasPermission('oms:sale-contract:query')")
public CommonResult<PageResult<SaleContractRespVO>> getSaleContractPage(@Valid SaleContractPageReqVO pageReqVO) {
PageResult<SaleContractDO> pageResult = saleContractService.getSaleContractPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, SaleContractRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出OMS销售合约 Excel")
@PreAuthorize("@ss.hasPermission('oms:sale-contract:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportSaleContractExcel(@Valid SaleContractPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<SaleContractDO> list = saleContractService.getSaleContractPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "OMS销售合约.xls", "数据", SaleContractRespVO.class,
BeanUtils.toBean(list, SaleContractRespVO.class));
}
// ==================== 子表OMS销售合约分录 ====================
@GetMapping("/sale-contract-entry/list-by-parent-id")
@Operation(summary = "获得OMS销售合约分录列表")
@Parameter(name = "parentId", description = "主表ID")
@PreAuthorize("@ss.hasPermission('oms:sale-contract:query')")
public CommonResult<List<SaleContractEntryDO>> getSaleContractEntryListByParentId(@RequestParam("parentId") Long parentId) {
return success(saleContractService.getSaleContractEntryListByParentId(parentId));
}
}

View File

@ -0,0 +1,60 @@
package cn.hangtag.module.oms.controller.admin.salecontract.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.hangtag.framework.common.pojo.PageParam;
import java.math.BigDecimal;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.hangtag.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - OMS销售合约分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SaleContractPageReqVO extends PageParam {
@Schema(description = "合约编号")
private String billno;
@Schema(description = "客户名称", example = "张三")
private String customerName;
@Schema(description = "客户采购编号")
private String customerBuyNo;
@Schema(description = "业务日期")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] bizdate;
@Schema(description = "甲方")
private String partyA;
@Schema(description = "乙方")
private String partyB;
@Schema(description = "负责人")
private String head;
@Schema(description = "职员")
private String clerk;
@Schema(description = "手机")
private String phone;
@Schema(description = "传真")
private String fax;
@Schema(description = "金额")
private BigDecimal amount;
@Schema(description = "地址")
private String address;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,72 @@
package cn.hangtag.module.oms.controller.admin.salecontract.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import java.math.BigDecimal;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*;
@Schema(description = "管理后台 - OMS销售合约 Response VO")
@Data
@ExcelIgnoreUnannotated
public class SaleContractRespVO {
@Schema(description = "合约编号")
@ExcelProperty("合约编号")
private String billno;
@Schema(description = "客户id", example = "21638")
@ExcelProperty("客户id")
private Long customerId;
@Schema(description = "客户名称", example = "张三")
@ExcelProperty("客户名称")
private String customerName;
@Schema(description = "客户采购编号")
@ExcelProperty("客户采购编号")
private String customerBuyNo;
@Schema(description = "业务日期")
@ExcelProperty("业务日期")
private LocalDateTime bizdate;
@Schema(description = "甲方")
@ExcelProperty("甲方")
private String partyA;
@Schema(description = "乙方")
@ExcelProperty("乙方")
private String partyB;
@Schema(description = "负责人")
@ExcelProperty("负责人")
private String head;
@Schema(description = "职员")
@ExcelProperty("职员")
private String clerk;
@Schema(description = "手机")
@ExcelProperty("手机")
private String phone;
@Schema(description = "传真")
@ExcelProperty("传真")
private String fax;
@Schema(description = "金额")
@ExcelProperty("金额")
private BigDecimal amount;
@Schema(description = "地址")
@ExcelProperty("地址")
private String address;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,61 @@
package cn.hangtag.module.oms.controller.admin.salecontract.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import javax.validation.constraints.*;
import java.math.BigDecimal;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import cn.hangtag.module.oms.dal.dataobject.salecontractentry.SaleContractEntryDO;
@Schema(description = "管理后台 - OMS销售合约新增/修改 Request VO")
@Data
public class SaleContractSaveReqVO {
@Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "31958")
private Long id;
@Schema(description = "合约编号")
private String billno;
@Schema(description = "客户id", example = "21638")
private Long customerId;
@Schema(description = "客户名称", example = "张三")
private String customerName;
@Schema(description = "客户采购编号")
private String customerBuyNo;
@Schema(description = "业务日期")
private LocalDateTime bizdate;
@Schema(description = "甲方")
private String partyA;
@Schema(description = "乙方")
private String partyB;
@Schema(description = "负责人")
private String head;
@Schema(description = "职员")
private String clerk;
@Schema(description = "手机")
private String phone;
@Schema(description = "传真")
private String fax;
@Schema(description = "金额")
private BigDecimal amount;
@Schema(description = "地址")
private String address;
@Schema(description = "OMS销售合约分录列表")
private List<SaleContractEntryDO> saleContractEntrys;
}

View File

@ -141,11 +141,6 @@ public class SaleOrderController {
return success(true);
}
/**
* 生成PDF文档并下载
* @param response HTTP响应

View File

@ -0,0 +1,37 @@
package cn.hangtag.module.oms.controller.admin.trade;
import cn.hangtag.framework.common.pojo.CommonResult;
import cn.hangtag.module.oms.controller.admin.common.vo.DataComparisonRespVO;
import cn.hangtag.module.oms.controller.admin.trade.vo.TradeOrderSummaryRespVO;
import cn.hangtag.module.oms.service.saleorder.SaleOrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import static cn.hangtag.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 订单统计")
@RestController
@RequestMapping("/oms/statistics/trade")
@Validated
@Slf4j
public class TradeStatisticsController {
@Resource
private SaleOrderService saleOrderService;
@GetMapping("/order-comparison")
@Operation(summary = "获得交易订单数量")
//@PreAuthorize("@ss.hasPermission('statistics:order:query')")
public CommonResult<DataComparisonRespVO<TradeOrderSummaryRespVO>> getOrderComparison() {
return success(saleOrderService.getOrderComparison());
}
}

View File

@ -0,0 +1,22 @@
package cn.hangtag.module.oms.controller.admin.trade.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 交易订单数量 Response VO")
@Data
public class TradeOrderCountRespVO {
@Schema(description = "待发货", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long undelivered;
@Schema(description = "待核销", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long pickUp;
@Schema(description = "退款中", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long afterSaleApply;
@Schema(description = "提现待审核", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long auditingWithdraw;
}

View File

@ -0,0 +1,16 @@
package cn.hangtag.module.oms.controller.admin.trade.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 交易订单统计 Response VO")
@Data
public class TradeOrderSummaryRespVO {
@Schema(description = "支付订单商品数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer orderPayCount;
@Schema(description = "总支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer orderPayPrice;
}

View File

@ -0,0 +1,30 @@
package cn.hangtag.module.oms.controller.admin.trade.vo;
import cn.hangtag.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.hangtag.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 交易订单量趋势统计 Request VO")
@Data
public class TradeOrderTrendReqVO {
@Schema(description = "日期范围类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "日期范围类型不能为空")
private Integer type;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "起始时间")
private LocalDateTime beginTime;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "截止时间")
private LocalDateTime endTime;
}

View File

@ -0,0 +1,19 @@
package cn.hangtag.module.oms.controller.admin.trade.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 订单量趋势统计 Response VO")
@Data
public class TradeOrderTrendRespVO {
@Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private String date;
@Schema(description = "订单数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer orderPayCount;
@Schema(description = "订单支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer orderPayPrice;
}

View File

@ -0,0 +1,20 @@
package cn.hangtag.module.oms.controller.admin.trade.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 交易统计 Response VO")
@Data
public class TradeSummaryRespVO {
@Schema(description = "昨日订单数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer yesterdayOrderCount;
@Schema(description = "昨日支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer yesterdayPayPrice;
@Schema(description = "本月订单数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer monthOrderCount;
@Schema(description = "本月支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer monthPayPrice;
}

View File

@ -0,0 +1,19 @@
package cn.hangtag.module.oms.controller.admin.trade.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.hangtag.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 交易状况 Request VO")
@Data
public class TradeTrendReqVO {
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "时间范围")
private LocalDateTime[] times;
}

View File

@ -0,0 +1,45 @@
package cn.hangtag.module.oms.controller.admin.trade.vo;
import cn.hangtag.framework.excel.core.convert.MoneyConvert;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import lombok.Data;
import java.time.LocalDate;
import static cn.hangtag.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
/**
* 交易状况统计 Excel VO
*
* @author owen
*/
@Data
public class TradeTrendSummaryExcelVO {
@ExcelProperty(value = "日期")
@DateTimeFormat(FORMAT_YEAR_MONTH_DAY)
private LocalDate date;
@ExcelProperty(value = "营业额", converter = MoneyConvert.class)
private Integer turnoverPrice;
@ExcelProperty(value = "商品支付金额", converter = MoneyConvert.class)
private Integer orderPayPrice;
@ExcelProperty(value = "充值金额", converter = MoneyConvert.class)
private Integer rechargePrice;
@ExcelProperty(value = "支出金额", converter = MoneyConvert.class)
private Integer expensePrice;
@ExcelProperty(value = "余额支付金额", converter = MoneyConvert.class)
private Integer walletPayPrice;
@ExcelProperty(value = "支付佣金金额", converter = MoneyConvert.class)
private Integer brokerageSettlementPrice;
@ExcelProperty(value = "商品退款金额", converter = MoneyConvert.class)
private Integer afterSaleRefundPrice;
}

View File

@ -0,0 +1,41 @@
package cn.hangtag.module.oms.controller.admin.trade.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import static cn.hangtag.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - 交易状况统计 Response VO")
@Data
public class TradeTrendSummaryRespVO {
@Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-12-16")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate date;
@Schema(description = "营业额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer turnoverPrice; // 营业额 = 商品支付金额 + 充值金额
@Schema(description = "订单支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer orderPayPrice;
@Schema(description = "余额支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer walletPayPrice;
@Schema(description = "订单退款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer afterSaleRefundPrice;
@Schema(description = "支付佣金金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer brokerageSettlementPrice;
@Schema(description = "充值金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer rechargePrice;
@Schema(description = "支出金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer expensePrice; // 余额支付金额 + 支付佣金金额 + 商品退款金额
}

View File

@ -39,6 +39,10 @@ public class CustomerDO extends BaseDO {
* 公司
*/
private String company;
/**
* 公司地址
*/
private String companyAddress;
/**
* 邮箱
*/

View File

@ -0,0 +1,85 @@
package cn.hangtag.module.oms.dal.dataobject.salecontract;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.hangtag.framework.mybatis.core.dataobject.BaseDO;
/**
* OMS销售合约 DO
*
* @author wwb
*/
@TableName("oms_salecontract")
@KeySequence("oms_salecontract_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SaleContractDO extends BaseDO {
/**
* ID
*/
@TableId
private Long id;
/**
* 合约编号
*/
private String billno;
/**
* 客户id
*/
private String customerId;
/**
* 客户名称
*/
private String customerName;
/**
* 客户采购编号
*/
private String customerBuyNo;
/**
* 业务日期
*/
private LocalDateTime bizdate;
/**
* 甲方
*/
private String partyA;
/**
* 乙方
*/
private String partyB;
/**
* 负责人
*/
private String head;
/**
* 职员
*/
private String clerk;
/**
* 手机
*/
private String phone;
/**
* 传真
*/
private String fax;
/**
* 金额
*/
private BigDecimal amount;
/**
* 地址
*/
private String address;
}

View File

@ -0,0 +1,70 @@
package cn.hangtag.module.oms.dal.dataobject.salecontractentry;
import lombok.*;
import java.util.*;
import java.math.BigDecimal;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.hangtag.framework.mybatis.core.dataobject.BaseDO;
/**
* OMS销售合约分录 DO
*
* @author WWB
*/
@TableName("oms_salecontract_entry")
@KeySequence("oms_salecontract_entry_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SaleContractEntryDO extends BaseDO {
/**
* ID
*/
@TableId
private Long id;
/**
* 主表ID
*/
private Long parentId;
/**
* 产品ID
*/
private Long productId;
/**
* 产品编码
*/
private String productNumber;
/**
* 产品名称
*/
private String productName;
/**
* 数量
*/
private Integer qty;
/**
* 单位订价
*/
private BigDecimal price;
/**
* 折扣
*/
private BigDecimal discount;
/**
* 金额
*/
private BigDecimal amount;
/**
* 交货日期
*/
private LocalDateTime deliveryDate;
}

View File

@ -0,0 +1,28 @@
package cn.hangtag.module.oms.dal.mysql.salecontractentry;
import java.util.*;
import cn.hangtag.framework.common.pojo.PageResult;
import cn.hangtag.framework.common.pojo.PageParam;
import cn.hangtag.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.hangtag.framework.mybatis.core.mapper.BaseMapperX;
import cn.hangtag.module.oms.dal.dataobject.salecontractentry.SaleContractEntryDO;
import org.apache.ibatis.annotations.Mapper;
/**
* OMS销售合约分录 Mapper
*
* @author WWB
*/
@Mapper
public interface SaleContractEntryMapper extends BaseMapperX<SaleContractEntryDO> {
default List<SaleContractEntryDO> selectListByParentId(Long parentId) {
return selectList(SaleContractEntryDO::getParentId, parentId);
}
default int deleteByParentId(Long parentId) {
return delete(SaleContractEntryDO::getParentId, parentId);
}
}

View File

@ -0,0 +1,38 @@
package cn.hangtag.module.oms.dal.mysql.salecontract;
import java.util.*;
import cn.hangtag.framework.common.pojo.PageResult;
import cn.hangtag.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.hangtag.framework.mybatis.core.mapper.BaseMapperX;
import cn.hangtag.module.oms.dal.dataobject.salecontract.SaleContractDO;
import org.apache.ibatis.annotations.Mapper;
import cn.hangtag.module.oms.controller.admin.salecontract.vo.*;
/**
* OMS销售合约 Mapper
*
* @author wwb
*/
@Mapper
public interface SaleContractMapper extends BaseMapperX<SaleContractDO> {
default PageResult<SaleContractDO> selectPage(SaleContractPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<SaleContractDO>()
.eqIfPresent(SaleContractDO::getBillno, reqVO.getBillno())
.likeIfPresent(SaleContractDO::getCustomerName, reqVO.getCustomerName())
.eqIfPresent(SaleContractDO::getCustomerBuyNo, reqVO.getCustomerBuyNo())
.betweenIfPresent(SaleContractDO::getBizdate, reqVO.getBizdate())
.eqIfPresent(SaleContractDO::getPartyA, reqVO.getPartyA())
.eqIfPresent(SaleContractDO::getPartyB, reqVO.getPartyB())
.eqIfPresent(SaleContractDO::getHead, reqVO.getHead())
.eqIfPresent(SaleContractDO::getClerk, reqVO.getClerk())
.eqIfPresent(SaleContractDO::getPhone, reqVO.getPhone())
.eqIfPresent(SaleContractDO::getFax, reqVO.getFax())
.eqIfPresent(SaleContractDO::getAmount, reqVO.getAmount())
.eqIfPresent(SaleContractDO::getAddress, reqVO.getAddress())
.betweenIfPresent(SaleContractDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(SaleContractDO::getId));
}
}

View File

@ -4,10 +4,14 @@ import cn.hangtag.framework.common.pojo.PageResult;
import cn.hangtag.framework.mybatis.core.mapper.BaseMapperX;
import cn.hangtag.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.hangtag.module.oms.controller.admin.saleorder.vo.SaleOrderPageReqVO;
import cn.hangtag.module.oms.controller.admin.trade.vo.TradeOrderSummaryRespVO;
import cn.hangtag.module.oms.dal.dataobject.saleorder.SaleOrderDO;
import cn.hangtag.module.oms.enums.saleorder.SaleOrderStatusEnum;
import cn.hutool.core.util.ObjectUtil;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
/**
* 销售订单 Mapper
@ -33,12 +37,10 @@ public interface SaleOrderMapper extends BaseMapperX<SaleOrderDO> {
appendTabQuery(tabType, queryWrapper);
queryWrapper.orderByDesc(SaleOrderDO::getCreateTime);
return selectPage(reqVO,queryWrapper );
return selectPage(reqVO, queryWrapper);
}
/**
* 添加后台 Tab 选项的查询条件
*
@ -60,4 +62,10 @@ public interface SaleOrderMapper extends BaseMapperX<SaleOrderDO> {
}
}
TradeOrderSummaryRespVO selectOrderQtySummaryByOrderStatusAndCreateTimeBetween(@Param("orderStatus") String orderStatus,
@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
}

View File

@ -0,0 +1,66 @@
package cn.hangtag.module.oms.service.salecontract;
import java.util.*;
import javax.validation.*;
import cn.hangtag.module.oms.controller.admin.salecontract.vo.*;
import cn.hangtag.module.oms.dal.dataobject.salecontract.SaleContractDO;
import cn.hangtag.module.oms.dal.dataobject.salecontractentry.SaleContractEntryDO;
import cn.hangtag.framework.common.pojo.PageResult;
import cn.hangtag.framework.common.pojo.PageParam;
/**
* OMS销售合约 Service 接口
*
* @author wwb
*/
public interface SaleContractService {
/**
* 创建OMS销售合约
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createSaleContract(@Valid SaleContractSaveReqVO createReqVO);
/**
* 更新OMS销售合约
*
* @param updateReqVO 更新信息
*/
void updateSaleContract(@Valid SaleContractSaveReqVO updateReqVO);
/**
* 删除OMS销售合约
*
* @param id 编号
*/
void deleteSaleContract(Long id);
/**
* 获得OMS销售合约
*
* @param id 编号
* @return OMS销售合约
*/
SaleContractDO getSaleContract(Long id);
/**
* 获得OMS销售合约分页
*
* @param pageReqVO 分页查询
* @return OMS销售合约分页
*/
PageResult<SaleContractDO> getSaleContractPage(SaleContractPageReqVO pageReqVO);
// ==================== 子表OMS销售合约分录 ====================
/**
* 获得OMS销售合约分录列表
*
* @param parentId 主表ID
* @return OMS销售合约分录列表
*/
List<SaleContractEntryDO> getSaleContractEntryListByParentId(Long parentId);
}

View File

@ -0,0 +1,112 @@
package cn.hangtag.module.oms.service.salecontract;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import cn.hangtag.module.oms.controller.admin.salecontract.vo.*;
import cn.hangtag.module.oms.dal.dataobject.salecontract.SaleContractDO;
import cn.hangtag.module.oms.dal.dataobject.salecontractentry.SaleContractEntryDO;
import cn.hangtag.framework.common.pojo.PageResult;
import cn.hangtag.framework.common.pojo.PageParam;
import cn.hangtag.framework.common.util.object.BeanUtils;
import cn.hangtag.module.oms.dal.mysql.salecontract.SaleContractMapper;
import cn.hangtag.module.oms.dal.mysql.salecontractentry.SaleContractEntryMapper;
import static cn.hangtag.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.hangtag.module.oms.enums.ErrorCodeConstants.*;
/**
* OMS销售合约 Service 实现类
*
* @author wwb
*/
@Service
@Validated
public class SaleContractServiceImpl implements SaleContractService {
@Resource
private SaleContractMapper saleContractMapper;
@Resource
private SaleContractEntryMapper saleContractEntryMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createSaleContract(SaleContractSaveReqVO createReqVO) {
// 插入
SaleContractDO saleContract = BeanUtils.toBean(createReqVO, SaleContractDO.class);
saleContractMapper.insert(saleContract);
// 插入子表
createSaleContractEntryList(saleContract.getId(), createReqVO.getSaleContractEntrys());
// 返回
return saleContract.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateSaleContract(SaleContractSaveReqVO updateReqVO) {
// 校验存在
validateSaleContractExists(updateReqVO.getId());
// 更新
SaleContractDO updateObj = BeanUtils.toBean(updateReqVO, SaleContractDO.class);
saleContractMapper.updateById(updateObj);
// 更新子表
updateSaleContractEntryList(updateReqVO.getId(), updateReqVO.getSaleContractEntrys());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteSaleContract(Long id) {
// 校验存在
validateSaleContractExists(id);
// 删除
saleContractMapper.deleteById(id);
// 删除子表
deleteSaleContractEntryByParentId(id);
}
private void validateSaleContractExists(Long id) {
if (saleContractMapper.selectById(id) == null) {
throw exception(SALE_CONTRACT_NOT_EXISTS);
}
}
@Override
public SaleContractDO getSaleContract(Long id) {
return saleContractMapper.selectById(id);
}
@Override
public PageResult<SaleContractDO> getSaleContractPage(SaleContractPageReqVO pageReqVO) {
return saleContractMapper.selectPage(pageReqVO);
}
// ==================== 子表OMS销售合约分录 ====================
@Override
public List<SaleContractEntryDO> getSaleContractEntryListByParentId(Long parentId) {
return saleContractEntryMapper.selectListByParentId(parentId);
}
private void createSaleContractEntryList(Long parentId, List<SaleContractEntryDO> list) {
list.forEach(o -> o.setParentId(parentId));
saleContractEntryMapper.insertBatch(list);
}
private void updateSaleContractEntryList(Long parentId, List<SaleContractEntryDO> list) {
deleteSaleContractEntryByParentId(parentId);
list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下1id 冲突2updateTime 不更新
createSaleContractEntryList(parentId, list);
}
private void deleteSaleContractEntryByParentId(Long parentId) {
saleContractEntryMapper.deleteByParentId(parentId);
}
}

View File

@ -4,7 +4,10 @@ import java.io.IOException;
import java.util.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.*;
import cn.hangtag.module.oms.controller.admin.common.vo.DataComparisonRespVO;
import cn.hangtag.module.oms.controller.admin.saleorder.vo.*;
import cn.hangtag.module.oms.controller.admin.trade.vo.TradeOrderSummaryRespVO;
import cn.hangtag.module.oms.dal.dataobject.saleorder.SaleOrderDO;
import cn.hangtag.module.oms.dal.dataobject.saleorderentry.SaleOrderEntryDO;
import cn.hangtag.framework.common.pojo.PageResult;
@ -74,4 +77,11 @@ public interface SaleOrderService {
String generateHtmlContent();
void generatePdf(HttpServletResponse response, String htmlContent) throws IOException;
/**
* 交易订单销售额对照
*
* @return 销售额对照
*/
DataComparisonRespVO<TradeOrderSummaryRespVO> getOrderComparison();
}

View File

@ -2,35 +2,41 @@ package cn.hangtag.module.oms.service.saleorder;
import cn.hangtag.framework.common.pojo.PageResult;
import cn.hangtag.framework.common.util.object.BeanUtils;
import cn.hangtag.module.oms.common.utils.WKHtmlToPdfUtil;
import cn.hangtag.module.oms.controller.admin.common.vo.DataComparisonRespVO;
import cn.hangtag.module.oms.controller.admin.produceorder.vo.ProduceOrderSaveReqVO;
import cn.hangtag.module.oms.common.utils.NumberChineseFormatterUtils;
import cn.hangtag.module.oms.controller.admin.saleorder.vo.SaleOrderPageReqVO;
import cn.hangtag.module.oms.controller.admin.saleorder.vo.SaleOrderSaveReqVO;
import cn.hangtag.module.oms.controller.admin.trade.vo.TradeOrderSummaryRespVO;
import cn.hangtag.module.oms.dal.dataobject.saleorder.SaleOrderDO;
import cn.hangtag.module.oms.dal.dataobject.saleorderentry.SaleOrderEntryDO;
import cn.hangtag.module.oms.dal.mysql.saleorder.SaleOrderMapper;
import cn.hangtag.module.oms.dal.mysql.saleorderentry.SaleOrderEntryMapper;
import cn.hangtag.module.oms.enums.saleorder.SaleOrderStatusEnum;
import cn.hangtag.module.oms.service.produceorder.ProduceOrderService;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Maps;
import com.lowagie.text.pdf.PdfWriter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
@ -166,12 +172,38 @@ public class SaleOrderServiceImpl implements SaleOrderService {
@Override
public String generateHtmlContent() {
Context context = new Context();
context.setVariable("title", "PDF文档标题");
context.setVariable("content",
"这是PDF文档的主要内容"
+ "“成功并不是终点,失败也不是终结,最重要的是继续前行的勇气。在人生的旅途中,我们会遇到许多挑战与挫折,这些都是成长的必经之路。每一次跌倒都是一次学习的机会,每一次失败都为成功铺设了基础。只要我们保持信念,不断努力,最终会到达梦想的彼岸。无论前方的路有多么坎坷,只要心怀希望,我们就有无限的可能性去改变自己的命运,实现心中的理想。”"
+ "由Thymeleaf模板引擎渲染。"
+ "");
context.setVariable("title", "销售合约");
context.setVariable("partyACompany", "甲方(买方)公司");
context.setVariable("contractNo", "合约编号XXXXXXX");
context.setVariable("partyAAddress", "甲方地址XXXXXXXXXXXX");
context.setVariable("bizDate", DateUtil.date().toDateStr());
context.setVariable("customerNo", "托尼");
context.setVariable("headUserName", "美特斯");
context.setVariable("partyBCompany", "乙方(卖方)公司");
context.setVariable("phone", "17198644317");
context.setVariable("partyBAddress", "乙方地址XXXXXXXXXXX");
context.setVariable("fax", "CCCCCCCCCCCCC");
context.setVariable("clerk", "李四");
context.setVariable("saleOrderNo", "订单编号XXXXXXXXXX");
context.setVariable("totalAmount", "1891.98");
context.setVariable("zhTotalAmount", "合共人民币"+NumberChineseFormatterUtils.convertToChinese(new BigDecimal("1891.98"))+"");
for (int i = 1; i <= 3; i++) {
context.setVariable("item"+i, i+"");
context.setVariable("explain"+i, "1891.98");
context.setVariable("qty"+i, "1891.98");
context.setVariable("price"+i, "1891.98");
context.setVariable("discount"+i, "1891.98");
context.setVariable("amount"+i, "1891.98");
context.setVariable("deliverydate"+i, "1891.98");
context.setVariable("explain"+i+"1", "1891.98");
context.setVariable("explain"+i+"2", "1891.98");
context.setVariable("explain"+i+"3", "1891.98");
}
return templateEngine.process("pdf_template", context);
}
@ -180,25 +212,28 @@ public class SaleOrderServiceImpl implements SaleOrderService {
// 设置响应类型
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=generated.pdf");
String fileName = StrUtil.format("C:\\Users\\Admin\\Desktop\\1111111\\test\\test_{}",
new Date().getTime());
// 创建ITextRenderer实例
ITextRenderer renderer = new ITextRenderer();
String templatePath = fileName + ".html";
String pdfPath = fileName + ".pdf";
FileUtil.writeString(htmlContent,templatePath, "UTF-8");
WKHtmlToPdfUtil.convert(templatePath, pdfPath);
File file = FileUtil.file(pdfPath);
// 设置字体路径使用 classpath 加载字体
ITextFontResolver fontResolver = renderer.getFontResolver();
ClassPathResource fontResource = new ClassPathResource("fonts/SimSun.ttf");
System.out.println(fontResource.getFile().getAbsolutePath());
fontResolver.addFont(fontResource.getFile().getAbsolutePath(), "Identity-H", true);
renderer.setDocumentFromString(htmlContent);
renderer.layout();
// 输出PDF到响应输出流
try (OutputStream outputStream = response.getOutputStream()) {
renderer.createPDF(outputStream);
outputStream.flush();
// 将文件内容写入响应流
try (InputStream inputStream = new FileInputStream(file)) {
IoUtil.copy(inputStream, response.getOutputStream());
} catch (IOException e) {
// 异常处理
log.info("导出电子病历写入流失败,{}", e.getMessage());
}
// 导出完删除
//FileUtil.del(file);
//FileUtil.del(templatePath);
/*
// 设置输出PDF文件
OutputStream os = new FileOutputStream("index.pdf");
@ -209,6 +244,20 @@ public class SaleOrderServiceImpl implements SaleOrderService {
}
@Override
public DataComparisonRespVO<TradeOrderSummaryRespVO> getOrderComparison() {
return new DataComparisonRespVO<TradeOrderSummaryRespVO>()
.setValue(getOrderQtySummary(LocalDateTime.now()))
.setReference(getOrderQtySummary(LocalDateTime.now().minusDays(1)));
}
private TradeOrderSummaryRespVO getOrderQtySummary(LocalDateTime date) {
LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(date);
LocalDateTime endTime = LocalDateTimeUtil.endOfDay(date);
return saleOrderMapper.selectOrderQtySummaryByOrderStatusAndCreateTimeBetween(
null, beginTime, endTime);
}
private void createSaleOrderEntryList(Long parentId, List<SaleOrderEntryDO> list) {
list.forEach(o -> o.setParentId(parentId));

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.hangtag.module.oms.dal.mysql.salecontract.SaleContractMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>

View File

@ -2,11 +2,20 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.hangtag.module.oms.dal.mysql.saleorder.SaleOrderMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
<select id="selectOrderQtySummaryByOrderStatusAndCreateTimeBetween"
resultType="cn.hangtag.module.oms.controller.admin.trade.vo.TradeOrderSummaryRespVO">
SELECT IFNULL(SUM(0), 0) AS orderPayPrice,
COUNT(1) AS orderPayCount
FROM oms_saleorder
WHERE deleted = FALSE
<if test="orderStatus != null">
AND order_status = #{orderStatus}
</if>
AND create_time BETWEEN #{beginTime} AND #{endTime}
</select>
</mapper>

View File

@ -1,37 +1,300 @@
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title th:text="${title}">PDF文档</title>
<meta charset="UTF-8" />
<title th:text="${title}">销售合约</title>
<!-- 引入 Bootstrap -->
<style>
@page {
size: a4;
}
/* 内联CSS样式确保在PDF中正确渲染 */
body {
font-family: "SimSun", serif;
padding: 20px;
line-height: 1.6;
}
h1 {
text-align: center;
margin-bottom: 40px;
margin-bottom: 20px;
color: #333;
}
p {
font-size: 16px;
color: #555;
.row {
box-sizing: border-box;
overflow: hidden;
margin-left: 0 !important;
margin-right: 0 !important;
}
.footer {
text-align: center;
margin-top: 50px;
font-size: 12px;
color: #999;
.font-title {
font-size: 11.5px;
}
.border-bottom {
border-bottom: 2px solid #000;
/* 修改为您想要的颜色和宽度 */
}
.border-top {
border-top: 2px solid #000;
/* 修改为您想要的颜色和宽度 */
}
.fixed-bottom {
margin-top: 100px;
}
.itemtitle {
flex: 0 0 55%;
}
.table>:not(caption)>*>* {
padding: 0px 0px 0px 0px;
border-bottom-width: 0px;
}
</style>
</head>
<body>
<h1 th:text="${title}">PDF文档标题</h1>
<p th:text="${content}">这是PDF文档的内容部分由Thymeleaf模板引擎渲染。</p>
<div class="footer">
<p>© 2024 路条编程 - 保留所有权利。</p>
<h1 th:text="${title}">销售合约</h1>
<table style="width: 100%;">
<tr>
<td style="width: 10%;">甲方(买方)</td>
<td style="width: 40%;" th:text="': '+${partyACompany}">:</td>
<td style="width: 10%;">合约编号</td>
<td style="width: 40%;" th:text="': '+${contractNo}">:</td>
</tr>
<tr>
<td style="width: 10%;"></td>
<td style="width: 40%;" th:text="' '+${partyAAddress}"></td>
<td style="width: 10%;">日期</td>
<td style="width: 40%;" th:text="': '+${bizDate}">:</td>
</tr>
<tr>
<td style="width: 10%;"></td>
<td style="width: 40%;"></td>
<td style="width: 10%;">客户</td>
<td style="width: 40%;" th:text="': '+${customerNo}">:</td>
</tr>
<tr>
<td style="width: 10%;">负责人</td>
<td style="width: 90%;" colspan="3" th:text="': '+${headUserName}">:</td>
</tr>
<tr>
<td style="width: 10%;">乙方(卖方)</td>
<td style="width: 40%;" th:text="': '+${partyBCompany}">:</td>
<td style="width: 10%;">电话</td>
<td style="width: 40%;" th:text="': '+${phone}">:</td>
</tr>
<tr>
<td style="width: 10%;"></td>
<td style="width: 40%;" th:text="' '+${partyBAddress}"></td>
<td style="width: 10%;">传真</td>
<td style="width: 40%;" th:text="': '+${fax}">:</td>
</tr>
<tr>
<td style="width: 10%;">职员</td>
<td style="width: 90%;" colspan="3" th:text="': '+${clerk}">:</td>
</tr>
<tr>
<td style="width: 15%;">客户采购编号</td>
<td style="width: 85%;" colspan="3" th:text="': '+${saleOrderNo}">:</td>
</tr>
</table>
<div class="">
<table class="table" style="width: 100%; border-collapse: collapse;">
<thead style="border-bottom:1px solid black;">
<tr>
<th style="width: 4%;">项目</th>
<th style="width: 25%;">产品说明</th>
<th style="width: 5%;text-align: center;">数量</th>
<th style="width: 8%;text-align: center;">单位订价RMB</th>
<th style="width: 5%;text-align: center;">折扣%</th>
<th style="width: 7%;text-align: center;">金额RMB</th>
<th style="width: 7%;text-align: center;">交货日期</th>
</tr>
</thead>
<tbody>
<tr>
<td th:text="${item1}">1</td>
<td th:text="${explain1}">Doe</td>
<td style="text-align: center; " th:text="${qty1}">12344</td>
<td style="text-align: center; " th:text="${price1}">Doe</td>
<td style="text-align: center; " th:text="${discount1}">Doe</td>
<td style="text-align: center; " th:text="${amount1}">Doe</td>
<td style="text-align: center; " th:text="${deliverydate1}">2024-01-12</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain11}">跨越多列的标题</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain12}">跨越多列的标题</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain13}">跨越多列的标题</td>
</tr>
<tr>
<td th:text="${item2}">1</td>
<td th:text="${explain2}">Doe</td>
<td style="text-align: center; " th:text="${qty2}">12344</td>
<td style="text-align: center; " th:text="${price2}">Doe</td>
<td style="text-align: center; " th:text="${discount2}">Doe</td>
<td style="text-align: center; " th:text="${amount2}">Doe</td>
<td style="text-align: center; " th:text="${deliverydate2}">2024-01-12</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain21}">跨越多列的标题</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain22}">跨越多列的标题</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain23}">跨越多列的标题</td>
</tr>
<tr>
<td th:text="${item3}">1</td>
<td th:text="${explain3}">Doe</td>
<td style="text-align: center; " th:text="${qty3}">12344</td>
<td style="text-align: center; " th:text="${price3}">Doe</td>
<td style="text-align: center; " th:text="${discount3}">Doe</td>
<td style="text-align: center; " th:text="${amount3}">Doe</td>
<td style="text-align: center; " th:text="${deliverydate3}">2024-01-12</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain31}">跨越多列的标题</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain32}">跨越多列的标题</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain33}">跨越多列的标题</td>
</tr>
<tr>
<td th:text="${item4}">1</td>
<td th:text="${explain4}">Doe</td>
<td style="text-align: center; " th:text="${qty4}">12344</td>
<td style="text-align: center; " th:text="${price4}">Doe</td>
<td style="text-align: center; " th:text="${discount4}">Doe</td>
<td style="text-align: center; " th:text="${amount4}">Doe</td>
<td style="text-align: center; " th:text="${deliverydate4}">2024-01-12</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain41}">跨越多列的标题</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain42}">跨越多列的标题</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain43}">跨越多列的标题</td>
</tr>
<tr>
<td th:text="${item5}">1</td>
<td th:text="${explain5}">Doe</td>
<td style="text-align: center; " th:text="${qty5}">12344</td>
<td style="text-align: center; " th:text="${price5}">Doe</td>
<td style="text-align: center; " th:text="${discount5}">Doe</td>
<td style="text-align: center; " th:text="${amount5}">Doe</td>
<td style="text-align: center; " th:text="${deliverydate5}">2024-01-12</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain51}">跨越多列的标题</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain52}">跨越多列的标题</td>
</tr>
<tr>
<td></td>
<td scope="row" colspan="6" th:text="${explain53}">跨越多列的标题</td>
</tr>
</tbody>
</table>
</div>
<br />
<div class="row">
<div class="col-2 p-3 font-title" style="text-align: right;width: 100%;">
<div style="padding-right: 80px;">
<span style="font-weight: bold;font-size: 16px;">净金额:</span>
<span style="border-bottom: 1px solid #dddddd;padding-bottom: 12px;">
<span
style="font-weight: bold;font-size: 16px; padding: 8px 0px 8px 0px;border-top: 1px solid #dddddd;border-bottom: 2px solid #dddddd;">
<span style="margin-right: 80px;">RMB</span>
<span th:text="${totalAmount}">XXXXXXXxXX</span>
</span>
</span>
</div>
</div>
</div>
<br />
<div class="row">
<div class="col-12 p-2 font-title" style="margin-left: 80px;font-weight: bold;font-size: 16px;"
th:text="${zhTotalAmount}">
合共人民币XXXXXXXXXXXXXXXXXXX</div>
</div>
<div class="fixed-bottom">
<table style="width: 100%;">
<tr>
<td style="width: 13%;font-weight: bold;">甲方(买方)</td>
<td style="width: 35%;" th:text="':'+${partyACompany}">:</td>
<td style="width: 13%;font-weight: bold;">乙方(卖方)</td>
<td style="width: 35%;" th:text="':'+${partyBCompany}">:</td>
</tr>
<tr>
<td style="width: 13%;font-weight: bold;">代表</td>
<td style="width: 35%;">:</td>
<td style="width: 13%;font-weight: bold;">代表</td>
<td style="width: 35%;">:</td>
</tr>
<tr>
<td style="width: 13%;font-weight: bold;">日期</td>
<td style="width: 35%;">:</td>
<td style="width: 13%;font-weight: bold;">日期</td>
<td style="width: 35%;">:</td>
</tr>
</table>
<div class="row">
<p class="col-12 p-1 border " style="font-weight: bold;">请在合约上签名确认并传真送回,谢谢。</p>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,175 @@
package cn.hangtag.module.oms.service.salecontract;
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.salecontract.vo.*;
import cn.hangtag.module.oms.dal.dataobject.salecontract.SaleContractDO;
import cn.hangtag.module.oms.dal.mysql.salecontract.SaleContractMapper;
import cn.hangtag.framework.common.pojo.PageResult;
import javax.annotation.Resource;
import org.springframework.context.annotation.Import;
import java.math.BigDecimal;
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 SaleContractServiceImpl} 的单元测试类
*
* @author wwb
*/
@Import(SaleContractServiceImpl.class)
public class SaleContractServiceImplTest extends BaseDbUnitTest {
@Resource
private SaleContractServiceImpl saleContractService;
@Resource
private SaleContractMapper saleContractMapper;
@Test
public void testCreateSaleContract_success() {
// 准备参数
SaleContractSaveReqVO createReqVO = randomPojo(SaleContractSaveReqVO.class).setId(null);
// 调用
Long saleContractId = saleContractService.createSaleContract(createReqVO);
// 断言
assertNotNull(saleContractId);
// 校验记录的属性是否正确
SaleContractDO saleContract = saleContractMapper.selectById(saleContractId);
assertPojoEquals(createReqVO, saleContract, "id");
}
@Test
public void testUpdateSaleContract_success() {
// mock 数据
SaleContractDO dbSaleContract = randomPojo(SaleContractDO.class);
saleContractMapper.insert(dbSaleContract);// @Sql: 先插入出一条存在的数据
// 准备参数
SaleContractSaveReqVO updateReqVO = randomPojo(SaleContractSaveReqVO.class, o -> {
o.setId(dbSaleContract.getId()); // 设置更新的 ID
});
// 调用
saleContractService.updateSaleContract(updateReqVO);
// 校验是否更新正确
SaleContractDO saleContract = saleContractMapper.selectById(updateReqVO.getId()); // 获取最新的
assertPojoEquals(updateReqVO, saleContract);
}
@Test
public void testUpdateSaleContract_notExists() {
// 准备参数
SaleContractSaveReqVO updateReqVO = randomPojo(SaleContractSaveReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> saleContractService.updateSaleContract(updateReqVO), SALE_CONTRACT_NOT_EXISTS);
}
@Test
public void testDeleteSaleContract_success() {
// mock 数据
SaleContractDO dbSaleContract = randomPojo(SaleContractDO.class);
saleContractMapper.insert(dbSaleContract);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbSaleContract.getId();
// 调用
saleContractService.deleteSaleContract(id);
// 校验数据不存在了
assertNull(saleContractMapper.selectById(id));
}
@Test
public void testDeleteSaleContract_notExists() {
}
@Test
@Disabled // TODO 请修改 null 为需要的值然后删除 @Disabled 注解
public void testGetSaleContractPage() {
// mock 数据
SaleContractDO dbSaleContract = randomPojo(SaleContractDO.class, o -> { // 等会查询到
o.setBillno(null);
o.setCustomerName(null);
o.setCustomerBuyNo(null);
o.setBizdate(null);
o.setPartyA(null);
o.setPartyB(null);
o.setHead(null);
o.setClerk(null);
o.setPhone(null);
o.setFax(null);
o.setAmount(null);
o.setAddress(null);
o.setCreateTime(null);
});
saleContractMapper.insert(dbSaleContract);
// 测试 billno 不匹配
saleContractMapper.insert(cloneIgnoreId(dbSaleContract, o -> o.setBillno(null)));
// 测试 customerName 不匹配
saleContractMapper.insert(cloneIgnoreId(dbSaleContract, o -> o.setCustomerName(null)));
// 测试 customerBuyNo 不匹配
saleContractMapper.insert(cloneIgnoreId(dbSaleContract, o -> o.setCustomerBuyNo(null)));
// 测试 bizdate 不匹配
saleContractMapper.insert(cloneIgnoreId(dbSaleContract, o -> o.setBizdate(null)));
// 测试 partyA 不匹配
saleContractMapper.insert(cloneIgnoreId(dbSaleContract, o -> o.setPartyA(null)));
// 测试 partyB 不匹配
saleContractMapper.insert(cloneIgnoreId(dbSaleContract, o -> o.setPartyB(null)));
// 测试 head 不匹配
saleContractMapper.insert(cloneIgnoreId(dbSaleContract, o -> o.setHead(null)));
// 测试 clerk 不匹配
saleContractMapper.insert(cloneIgnoreId(dbSaleContract, o -> o.setClerk(null)));
// 测试 phone 不匹配
saleContractMapper.insert(cloneIgnoreId(dbSaleContract, o -> o.setPhone(null)));
// 测试 fax 不匹配
saleContractMapper.insert(cloneIgnoreId(dbSaleContract, o -> o.setFax(null)));
// 测试 amount 不匹配
saleContractMapper.insert(cloneIgnoreId(dbSaleContract, o -> o.setAmount(null)));
// 测试 address 不匹配
saleContractMapper.insert(cloneIgnoreId(dbSaleContract, o -> o.setAddress(null)));
// 测试 createTime 不匹配
saleContractMapper.insert(cloneIgnoreId(dbSaleContract, o -> o.setCreateTime(null)));
// 准备参数
SaleContractPageReqVO reqVO = new SaleContractPageReqVO();
reqVO.setBillno(null);
reqVO.setCustomerName(null);
reqVO.setCustomerBuyNo(null);
reqVO.setBizdate(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
reqVO.setPartyA(null);
reqVO.setPartyB(null);
reqVO.setHead(null);
reqVO.setClerk(null);
reqVO.setPhone(null);
reqVO.setFax(null);
reqVO.setAmount(null);
reqVO.setAddress(null);
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
// 调用
PageResult<SaleContractDO> pageResult = saleContractService.getSaleContractPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbSaleContract, pageResult.getList().get(0));
}
}

View File

@ -326,21 +326,7 @@ public class AdminUserServiceImpl implements AdminUserService {
private AdminUserDO validateUserForCreateOrUpdate(Long id, String username, String mobile, String email,
Long deptId, Set<Long> postIds) {
// 关闭数据权限避免因为没有数据权限查询不到数据进而导致唯一校验不正确
return DataPermissionUtils.executeIgnore(() -> {
// 校验用户存在
AdminUserDO user = validateUserExists(id);
// 校验用户名唯一
validateUsernameUnique(id, username);
// 校验手机号唯一
validateMobileUnique(id, mobile);
// 校验邮箱唯一
validateEmailUnique(id, email);
// 校验部门处于开启状态
deptService.validateDeptList(CollectionUtils.singleton(deptId));
// 校验岗位处于开启状态
postService.validatePostList(postIds);
return user;
});
return null;
}
@VisibleForTesting

View File

@ -4,8 +4,8 @@ import javax.annotation.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2024-08-13T22:38:55+0800",
comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_251 (Oracle Corporation)"
date = "2024-09-15T11:57:57+0800",
comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_401 (Oracle Corporation)"
)
public class OAuth2OpenConvertImpl implements OAuth2OpenConvert {
}

View File

@ -6,8 +6,8 @@ import javax.annotation.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2024-08-13T22:38:55+0800",
comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_251 (Oracle Corporation)"
date = "2024-09-15T11:57:57+0800",
comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_401 (Oracle Corporation)"
)
public class SocialUserConvertImpl implements SocialUserConvert {

View File

@ -43,9 +43,9 @@ spring:
primary: master
datasource:
master:
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
url: jdbc:mysql://202.74.40.60:33061/oms-uat?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
username: root
password: Admin11039
password: qygo5gYNhivG
# slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改
# lazy: true # 开启懒加载,保证启动速度
# url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例

View File

@ -4,6 +4,7 @@ import request from '@/config/axios'
export interface CustomerVO {
name: string // 名称
company: string // 公司
companyAddress: string // 公司
email: string // 邮箱
contacts: string // 联系人
gdperson: string // 跟单员

View File

@ -0,0 +1,59 @@
import request from '@/config/axios'
// OMS销售合约 VO
export interface SaleContractVO {
id: number // ID
billno: string // 合约编号
customerId: string // 客户id
customerName: string // 客户名称
customerBuyNo: string // 客户采购编号
bizdate: Date // 业务日期
partyA: string // 甲方
partyB: string // 乙方
head: string // 负责人
clerk: string // 职员
phone: string // 手机
fax: string // 传真
amount: number // 金额
address: string // 地址
}
// OMS销售合约 API
export const SaleContractApi = {
// 查询OMS销售合约分页
getSaleContractPage: async (params: any) => {
return await request.get({ url: `/oms/sale-contract/page`, params })
},
// 查询OMS销售合约详情
getSaleContract: async (id: number) => {
return await request.get({ url: `/oms/sale-contract/get?id=` + id })
},
// 新增OMS销售合约
createSaleContract: async (data: SaleContractVO) => {
return await request.post({ url: `/oms/sale-contract/create`, data })
},
// 修改OMS销售合约
updateSaleContract: async (data: SaleContractVO) => {
return await request.put({ url: `/oms/sale-contract/update`, data })
},
// 删除OMS销售合约
deleteSaleContract: async (id: number) => {
return await request.delete({ url: `/oms/sale-contract/delete?id=` + id })
},
// 导出OMS销售合约 Excel
exportSaleContract: async (params) => {
return await request.download({ url: `/oms/sale-contract/export-excel`, params })
},
// ==================== 子表OMS销售合约分录 ====================
// 获得OMS销售合约分录列表
getSaleContractEntryListByParentId: async (parentId) => {
return await request.get({ url: `/oms/sale-contract/sale-contract-entry/list-by-parent-id?parentId=` + parentId })
},
}

View File

@ -0,0 +1,5 @@
/** 数据对照 Response VO */
export interface DataComparisonRespVO<T> {
value: T
reference: T
}

View File

@ -0,0 +1,119 @@
import request from '@/config/axios'
import dayjs from 'dayjs'
import { formatDate } from '@/utils/formatTime'
import { DataComparisonRespVO } from '@/api/oms/statistics/common'
/** 交易统计 Response VO */
export interface TradeSummaryRespVO {
yesterdayOrderCount: number
monthOrderCount: number
yesterdayPayPrice: number
monthPayPrice: number
}
/** 交易状况 Request VO */
export interface TradeTrendReqVO {
times: [dayjs.ConfigType, dayjs.ConfigType]
}
/** 交易状况统计 Response VO */
export interface TradeTrendSummaryRespVO {
time: string
turnoverPrice: number
orderPayPrice: number
rechargePrice: number
expensePrice: number
walletPayPrice: number
brokerageSettlementPrice: number
afterSaleRefundPrice: number
}
/** 交易订单数量 Response VO */
export interface TradeOrderCountRespVO {
/** 待发货 */
undelivered?: number
/** 待核销 */
pickUp?: number
/** 退款中 */
afterSaleApply?: number
/** 提现待审核 */
auditingWithdraw?: number
}
/** 交易订单统计 Response VO */
export interface TradeOrderSummaryRespVO {
/** 支付订单商品数 */
orderPayCount?: number
/** 总支付金额,单位:分 */
orderPayPrice?: number
}
/** 订单量趋势统计 Response VO */
export interface TradeOrderTrendRespVO {
/** 日期 */
date: string
/** 订单数量 */
orderPayCount: number
/** 订单支付金额 */
orderPayPrice: number
}
// 查询交易统计
export const getTradeStatisticsSummary = () => {
return request.get<DataComparisonRespVO<TradeSummaryRespVO>>({
url: '/statistics/trade/summary'
})
}
// 获得交易状况统计
export const getTradeStatisticsAnalyse = (params: TradeTrendReqVO) => {
return request.get<DataComparisonRespVO<TradeTrendSummaryRespVO>>({
url: '/statistics/trade/analyse',
params: formatDateParam(params)
})
}
// 获得交易状况明细
export const getTradeStatisticsList = (params: TradeTrendReqVO) => {
return request.get<TradeTrendSummaryRespVO[]>({
url: '/statistics/trade/list',
params: formatDateParam(params)
})
}
// 导出交易状况明细
export const exportTradeStatisticsExcel = (params: TradeTrendReqVO) => {
return request.download({
url: '/statistics/trade/export-excel',
params: formatDateParam(params)
})
}
// 获得交易订单数量
export const getOrderCount = async () => {
return await request.get<TradeOrderCountRespVO>({ url: `/statistics/trade/order-count` })
}
// 获得交易订单数量对照
export const getOrderComparison = async () => {
return await request.get<DataComparisonRespVO<TradeOrderSummaryRespVO>>({
url: `/oms/statistics/trade/order-comparison`
})
}
// 获得订单量趋势统计
export const getOrderCountTrendComparison = (
type: number,
beginTime: dayjs.ConfigType,
endTime: dayjs.ConfigType
) => {
return request.get<DataComparisonRespVO<TradeOrderTrendRespVO>[]>({
url: '/statistics/trade/order-count-trend',
params: { type, beginTime: formatDate(beginTime), endTime: formatDate(endTime) }
})
}
/** 时间参数需要格式化, 确保接口能识别 */
const formatDateParam = (params: TradeTrendReqVO) => {
return { times: [formatDate(params.times[0]), formatDate(params.times[1])] } as TradeTrendReqVO
}

View File

@ -1,243 +1,52 @@
<template>
<div>
<el-card shadow="never">
<el-skeleton :loading="loading" animated>
<el-row :gutter="16" justify="space-between">
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="flex items-center">
<div>
<div class="text-20px">
{{ t('workplace.welcome') }} {{ username }} {{ t('workplace.happyDay') }}
</div>
<div class="mt-10px text-14px text-gray-500">
{{ t('workplace.toady') }}20 - 32
</div>
</div>
</div>
</el-col>
<div class="flex flex-col">
<!-- 数据对照 -->
<el-row :gutter="16" class="row">
<el-col :md="6" :sm="12" :xs="24" :loading="loading">
<ComparisonCard
tag="今日"
title="订单量"
:value="orderComparison?.value?.orderPayCount || 0"
:reference="orderComparison?.reference?.orderPayCount || 0"
/>
</el-col>
</el-row>
</el-row>
</el-skeleton>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { set } from 'lodash-es'
import { EChartsOption } from 'echarts'
import { formatTime } from '@/utils'
import DraftDesign from '@/components/DraftDesign/index.vue'
import * as TradeStatisticsApi from '@/api/oms/statistics/trade'
import {TradeOrderSummaryRespVO} from '@/api/oms/statistics/trade'
import {DataComparisonRespVO} from '@/api/oms/statistics/common'
import ComparisonCard from './components/ComparisonCard.vue'
import { useUserStore } from '@/store/modules/user'
import { useWatermark } from '@/hooks/web/useWatermark'
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
import { pieOptions, barOptions } from './echarts-data'
/** 商城首页 */
defineOptions({ name: 'MallHome' })
defineOptions({ name: 'Home' })
const loading = ref(true) //
const orderComparison = ref<DataComparisonRespVO<TradeOrderSummaryRespVO>>() //
const { t } = useI18n()
const userStore = useUserStore()
const { setWatermark } = useWatermark()
const loading = ref(true)
const avatar = userStore.getUser.avatar
const username = userStore.getUser.nickname
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
//
let totalSate = reactive<WorkplaceTotal>({
project: 0,
access: 0,
todo: 0
})
const getCount = async () => {
const data = {
project: 40,
access: 2340,
todo: 10
}
totalSate = Object.assign(totalSate, data)
/** 查询交易对照卡片数据 */
const getOrderComparison = async () => {
orderComparison.value = await TradeStatisticsApi.getOrderComparison()
}
//
let projects = reactive<Project[]>([])
const getProject = async () => {
const data = [
{
name: 'ruoyi-vue-pro',
icon: 'akar-icons:github-fill',
message: 'https://github.com/YunaiV/ruoyi-vue-pro',
personal: 'Spring Boot 单体架构',
time: new Date()
},
{
name: 'yudao-ui-admin-vue3',
icon: 'logos:vue',
message: 'https://github.com/yudaocode/yudao-ui-admin-vue3',
personal: 'Vue3 + element-plus',
time: new Date()
},
{
name: 'yudao-ui-admin-vben',
icon: 'logos:vue',
message: 'https://github.com/yudaocode/yudao-ui-admin-vben',
personal: 'Vue3 + vben(antd)',
time: new Date()
},
{
name: 'yudao-cloud',
icon: 'akar-icons:github',
message: 'https://github.com/YunaiV/yudao-cloud',
personal: 'Spring Cloud 微服务架构',
time: new Date()
},
{
name: 'yudao-ui-mall-uniapp',
icon: 'logos:vue',
message: 'https://github.com/yudaocode/yudao-ui-admin-uniapp',
personal: 'Vue3 + uniapp',
time: new Date()
},
{
name: 'yudao-ui-admin-vue2',
icon: 'logos:vue',
message: 'https://github.com/yudaocode/yudao-ui-admin-vue2',
personal: 'Vue2 + element-ui',
time: new Date()
}
]
projects = Object.assign(projects, data)
}
/** 查询会员用户数量对照卡片数据 */
/*const getUserCountComparison = async () => {
userComparison.value = await MemberStatisticsApi.getUserCountComparison()
}*/
//
let notice = reactive<Notice[]>([])
const getNotice = async () => {
const data = [
{
title: '系统支持 JDK 8/17/21Vue 2/3',
type: '通知',
keys: ['通知', '8', '17', '21', '2', '3'],
date: new Date()
},
{
title: '后端提供 Spring Boot 2.7/3.2 + Cloud 双架构',
type: '公告',
keys: ['公告', 'Boot', 'Cloud'],
date: new Date()
},
{
title: '全部开源,个人与企业可 100% 直接使用,无需授权',
type: '通知',
keys: ['通知', '无需授权'],
date: new Date()
},
{
title: '国内使用最广泛的快速开发平台,超 300+ 人贡献',
type: '公告',
keys: ['公告', '最广泛'],
date: new Date()
}
]
notice = Object.assign(notice, data)
}
//
let shortcut = reactive<Shortcut[]>([])
const getShortcut = async () => {
const data = [
{
name: 'Github',
icon: 'akar-icons:github-fill',
url: 'github.io'
},
{
name: 'Vue',
icon: 'logos:vue',
url: 'vuejs.org'
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite',
url: 'https://vitejs.dev/'
},
{
name: 'Angular',
icon: 'logos:angular-icon',
url: 'github.io'
},
{
name: 'React',
icon: 'logos:react',
url: 'github.io'
},
{
name: 'Webpack',
icon: 'logos:webpack',
url: 'github.io'
}
]
shortcut = Object.assign(shortcut, data)
}
//
const getUserAccessSource = async () => {
const data = [
{ value: 335, name: 'analysis.directAccess' },
{ value: 310, name: 'analysis.mailMarketing' },
{ value: 234, name: 'analysis.allianceAdvertising' },
{ value: 135, name: 'analysis.videoAdvertising' },
{ value: 1548, name: 'analysis.searchEngines' }
]
set(
pieOptionsData,
'legend.data',
data.map((v) => t(v.name))
)
pieOptionsData!.series![0].data = data.map((v) => {
return {
name: t(v.name),
value: v.value
}
})
}
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
//
const getWeeklyUserActivity = async () => {
const data = [
{ value: 13253, name: 'analysis.monday' },
{ value: 34235, name: 'analysis.tuesday' },
{ value: 26321, name: 'analysis.wednesday' },
{ value: 12340, name: 'analysis.thursday' },
{ value: 24643, name: 'analysis.friday' },
{ value: 1322, name: 'analysis.saturday' },
{ value: 1324, name: 'analysis.sunday' }
]
set(
barOptionsData,
'xAxis.data',
data.map((v) => t(v.name))
)
set(barOptionsData, 'series', [
{
name: t('analysis.activeQuantity'),
data: data.map((v) => v.value),
type: 'bar'
}
])
}
const getAllApi = async () => {
await Promise.all([
getCount(),
getProject(),
getNotice(),
getShortcut(),
getUserAccessSource(),
getWeeklyUserActivity()
])
/** 初始化 **/
onMounted(async () => {
loading.value = true
await Promise.all([getOrderComparison()])
loading.value = false
}
getAllApi()
})
</script>
<style lang="scss" scoped>
.row {
.el-col {
margin-bottom: 1rem;
}
}
</style>

View File

@ -0,0 +1,42 @@
<template>
<div class="flex flex-col gap-2 bg-[var(--el-bg-color-overlay)] p-6">
<div class="flex items-center justify-between text-gray-500">
<span>{{ title }}</span>
<el-tag>{{ tag }}</el-tag>
</div>
<div class="flex flex-row items-baseline justify-between">
<CountTo :prefix="prefix" :end-val="value" :decimals="decimals" class="text-3xl" />
<span :class="toNumber(percent) > 0 ? 'text-red-500' : 'text-green-500'">
{{ Math.abs(toNumber(percent)) }}%
<Icon :icon="toNumber(percent) > 0 ? 'ep:caret-top' : 'ep:caret-bottom'" class="!text-sm" />
</span>
</div>
<el-divider class="mb-1! mt-2!" />
<div class="flex flex-row items-center justify-between text-sm">
<span class="text-gray-500">昨日数据</span>
<span>{{ prefix || '' }}{{ reference }}</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { toNumber } from 'lodash-es'
import { calculateRelativeRate } from '@/utils'
/** 交易对照卡片 */
defineOptions({ name: 'ComparisonCard' })
const props = defineProps({
title: propTypes.string.def('').isRequired,
tag: propTypes.string.def(''),
prefix: propTypes.string.def(''),
value: propTypes.number.def(0).isRequired,
reference: propTypes.number.def(0).isRequired,
decimals: propTypes.number.def(0)
})
//
const percent = computed(() =>
calculateRelativeRate(props.value as number, props.reference as number)
)
</script>

View File

@ -0,0 +1,91 @@
<template>
<el-card shadow="never">
<template #header>
<CardTitle title="用户统计" />
</template>
<!-- 折线图 -->
<Echart :height="300" :options="lineChartOptions" />
</el-card>
</template>
<script lang="ts" setup>
import dayjs from 'dayjs'
import { EChartsOption } from 'echarts'
import * as MemberStatisticsApi from '@/api/mall/statistics/member'
import { formatDate } from '@/utils/formatTime'
import { CardTitle } from '@/components/Card'
/** 会员用户统计卡片 */
defineOptions({ name: 'MemberStatisticsCard' })
const loading = ref(true) //
/** 折线图配置 */
const lineChartOptions = reactive<EChartsOption>({
dataset: {
dimensions: ['date', 'count'],
source: []
},
grid: {
left: 20,
right: 20,
bottom: 20,
top: 80,
containLabel: true
},
legend: {
top: 50
},
series: [{ name: '注册量', type: 'line', smooth: true, areaStyle: {} }],
toolbox: {
feature: {
//
dataZoom: {
yAxisIndex: false // Y
},
brush: {
type: ['lineX', 'clear'] //
},
saveAsImage: { show: true, name: '会员统计' } //
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
padding: [5, 10]
},
xAxis: {
type: 'category',
boundaryGap: false,
axisTick: {
show: false
},
axisLabel: {
formatter: (date: string) => formatDate(date, 'MM-DD')
}
},
yAxis: {
axisTick: {
show: false
}
}
}) as EChartsOption
const getMemberRegisterCountList = async () => {
loading.value = true
//
const beginTime = dayjs().subtract(30, 'd').startOf('d')
const endTime = dayjs().endOf('d')
const list = await MemberStatisticsApi.getMemberRegisterCountList(beginTime, endTime)
// Echarts
if (lineChartOptions.dataset && lineChartOptions.dataset['source']) {
lineChartOptions.dataset['source'] = list
}
loading.value = false
}
/** 初始化 **/
onMounted(() => {
getMemberRegisterCountList()
})
</script>

View File

@ -0,0 +1,107 @@
<template>
<el-card shadow="never">
<template #header>
<CardTitle title="运营数据" />
</template>
<div class="flex flex-row flex-wrap items-center gap-8 p-4">
<div
v-for="item in data"
:key="item.name"
class="h-20 w-20% flex flex-col cursor-pointer items-center justify-center gap-2"
@click="handleClick(item.routerName)"
>
<CountTo
:prefix="item.prefix"
:end-val="item.value"
:decimals="item.decimals"
class="text-3xl"
/>
<span class="text-center">{{ item.name }}</span>
</div>
</div>
</el-card>
</template>
<script lang="ts" setup>
import * as ProductSpuApi from '@/api/mall/product/spu'
import * as TradeStatisticsApi from '@/api/mall/statistics/trade'
import * as PayStatisticsApi from '@/api/mall/statistics/pay'
import { CardTitle } from '@/components/Card'
/** 运营数据卡片 */
defineOptions({ name: 'OperationDataCard' })
const router = useRouter() //
/** 数据 */
const data = reactive({
orderUndelivered: { name: '待发货订单', value: 9, routerName: 'TradeOrder' },
orderAfterSaleApply: { name: '退款中订单', value: 4, routerName: 'TradeAfterSale' },
orderWaitePickUp: { name: '待核销订单', value: 0, routerName: 'TradeOrder' },
productAlertStock: { name: '库存预警', value: 0, routerName: 'ProductSpu' },
productForSale: { name: '上架商品', value: 0, routerName: 'ProductSpu' },
productInWarehouse: { name: '仓库商品', value: 0, routerName: 'ProductSpu' },
withdrawAuditing: { name: '提现待审核', value: 0, routerName: 'TradeBrokerageWithdraw' },
rechargePrice: {
name: '账户充值',
value: 0.0,
prefix: '¥',
decimals: 2,
routerName: 'PayWalletRecharge'
}
})
/** 查询订单数据 */
const getOrderData = async () => {
const orderCount = await TradeStatisticsApi.getOrderCount()
if (orderCount.undelivered != null) {
data.orderUndelivered.value = orderCount.undelivered
}
if (orderCount.afterSaleApply != null) {
data.orderAfterSaleApply.value = orderCount.afterSaleApply
}
if (orderCount.pickUp != null) {
data.orderWaitePickUp.value = orderCount.pickUp
}
if (orderCount.auditingWithdraw != null) {
data.withdrawAuditing.value = orderCount.auditingWithdraw
}
}
/** 查询商品数据 */
const getProductData = async () => {
// TODO: @
const productCount = await ProductSpuApi.getTabsCount()
data.productForSale.value = productCount['0']
data.productInWarehouse.value = productCount['1']
data.productAlertStock.value = productCount['3']
}
/** 查询钱包充值数据 */
const getWalletRechargeData = async () => {
const paySummary = await PayStatisticsApi.getWalletRechargePrice()
data.rechargePrice.value = paySummary.rechargePrice
}
/**
* 跳转到对应页面
*
* @param routerName 路由页面组件的名称
*/
const handleClick = (routerName: string) => {
router.push({ name: routerName })
}
/** 激活时 */
onActivated(() => {
getOrderData()
getProductData()
getWalletRechargeData()
})
/** 初始化 **/
onMounted(() => {
getOrderData()
getProductData()
getWalletRechargeData()
})
</script>

View File

@ -0,0 +1,82 @@
<template>
<el-card shadow="never">
<template #header>
<CardTitle title="快捷入口" />
</template>
<div class="flex flex-row flex-wrap gap-8 p-4">
<div
v-for="menu in menuList"
:key="menu.name"
class="h-20 w-20% flex flex-col cursor-pointer items-center justify-center gap-2"
@click="handleMenuClick(menu.routerName)"
>
<div
:class="menu.bgColor"
class="h-48px w-48px flex items-center justify-center rounded text-white"
>
<Icon :icon="menu.icon" class="text-7.5!" />
</div>
<span>{{ menu.name }}</span>
</div>
</div>
</el-card>
</template>
<script lang="ts" setup>
/** 快捷入口卡片 */
import { CardTitle } from '@/components/Card'
defineOptions({ name: 'ShortcutCard' })
const router = useRouter() //
/** 菜单列表 */
const menuList = [
{ name: '用户管理', icon: 'ep:user-filled', bgColor: 'bg-red-400', routerName: 'MemberUser' },
{
name: '商品管理',
icon: 'fluent-mdl2:product',
bgColor: 'bg-orange-400',
routerName: 'ProductSpu'
},
{ name: '订单管理', icon: 'ep:list', bgColor: 'bg-yellow-500', routerName: 'TradeOrder' },
{
name: '售后管理',
icon: 'ri:refund-2-line',
bgColor: 'bg-green-600',
routerName: 'TradeAfterSale'
},
{
name: '分销管理',
icon: 'fa-solid:project-diagram',
bgColor: 'bg-cyan-500',
routerName: 'TradeBrokerageUser'
},
{
name: '优惠券',
icon: 'ep:ticket',
bgColor: 'bg-blue-500',
routerName: 'PromotionCoupon'
},
{
name: '拼团活动',
icon: 'fa:group',
bgColor: 'bg-purple-500',
routerName: 'PromotionBargainActivity'
},
{
name: '佣金提现',
icon: 'vaadin:money-withdraw',
bgColor: 'bg-rose-500',
routerName: 'TradeBrokerageWithdraw'
}
]
/**
* 跳转到菜单对应页面
*
* @param routerName 路由页面组件的名称
*/
const handleMenuClick = (routerName: string) => {
router.push({ name: routerName })
}
</script>

View File

@ -0,0 +1,208 @@
<template>
<el-card shadow="never">
<template #header>
<div class="flex flex-row items-center justify-between">
<CardTitle title="交易量趋势" />
<!-- 查询条件 -->
<div class="flex flex-row items-center gap-2">
<el-radio-group v-model="timeRangeType" @change="handleTimeRangeTypeChange">
<el-radio-button v-for="[key, value] in timeRange.entries()" :key="key" :label="key">
{{ value.name }}
</el-radio-button>
</el-radio-group>
</div>
</div>
</template>
<!-- 折线图 -->
<Echart :height="300" :options="eChartOptions" />
</el-card>
</template>
<script lang="ts" setup>
import dayjs, { Dayjs } from 'dayjs'
import { EChartsOption } from 'echarts'
import * as TradeStatisticsApi from '@/api/mall/statistics/trade'
import { fenToYuan } from '@/utils'
import { formatDate } from '@/utils/formatTime'
import { CardTitle } from '@/components/Card'
/** 交易量趋势 */
defineOptions({ name: 'TradeTrendCard' })
enum TimeRangeTypeEnum {
DAY30 = 1,
WEEK = 7,
MONTH = 30,
YEAR = 365
} //
const timeRangeType = ref(TimeRangeTypeEnum.DAY30) // , 30
const loading = ref(true) //
// Map
const timeRange = new Map()
.set(TimeRangeTypeEnum.DAY30, {
name: '30天',
series: [
{ name: '订单金额', type: 'bar', smooth: true, data: [] },
{ name: '订单数量', type: 'line', smooth: true, data: [] }
]
})
.set(TimeRangeTypeEnum.WEEK, {
name: '周',
series: [
{ name: '上周金额', type: 'bar', smooth: true, data: [] },
{ name: '本周金额', type: 'bar', smooth: true, data: [] },
{ name: '上周数量', type: 'line', smooth: true, data: [] },
{ name: '本周数量', type: 'line', smooth: true, data: [] }
]
})
.set(TimeRangeTypeEnum.MONTH, {
name: '月',
series: [
{ name: '上月金额', type: 'bar', smooth: true, data: [] },
{ name: '本月金额', type: 'bar', smooth: true, data: [] },
{ name: '上月数量', type: 'line', smooth: true, data: [] },
{ name: '本月数量', type: 'line', smooth: true, data: [] }
]
})
.set(TimeRangeTypeEnum.YEAR, {
name: '年',
series: [
{ name: '去年金额', type: 'bar', smooth: true, data: [] },
{ name: '今年金额', type: 'bar', smooth: true, data: [] },
{ name: '去年数量', type: 'line', smooth: true, data: [] },
{ name: '今年数量', type: 'line', smooth: true, data: [] }
]
})
/** 图表配置 */
const eChartOptions = reactive<EChartsOption>({
grid: {
left: 20,
right: 20,
bottom: 20,
top: 80,
containLabel: true
},
legend: {
top: 50,
data: []
},
series: [],
toolbox: {
feature: {
//
dataZoom: {
yAxisIndex: false // Y
},
brush: {
type: ['lineX', 'clear'] //
},
saveAsImage: { show: true, name: '订单量趋势' } //
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
padding: [5, 10]
},
xAxis: {
type: 'category',
inverse: true,
boundaryGap: false,
axisTick: {
show: false
},
data: [],
axisLabel: {
formatter: (date: string) => {
switch (timeRangeType.value) {
case TimeRangeTypeEnum.DAY30:
return formatDate(date, 'MM-DD')
case TimeRangeTypeEnum.WEEK:
let weekDay = formatDate(date, 'ddd')
if (weekDay == '0') weekDay = '日'
return '周' + weekDay
case TimeRangeTypeEnum.MONTH:
return formatDate(date, 'D')
case TimeRangeTypeEnum.YEAR:
return formatDate(date, 'M') + '月'
default:
return date
}
}
}
},
yAxis: {
axisTick: {
show: false
}
}
}) as EChartsOption
/** 时间范围类型单选按钮选中 */
const handleTimeRangeTypeChange = async () => {
//
let beginTime: Dayjs
let endTime: Dayjs
switch (timeRangeType.value) {
case TimeRangeTypeEnum.WEEK:
beginTime = dayjs().startOf('week')
endTime = dayjs().endOf('week')
break
case TimeRangeTypeEnum.MONTH:
beginTime = dayjs().startOf('month')
endTime = dayjs().endOf('month')
break
case TimeRangeTypeEnum.YEAR:
beginTime = dayjs().startOf('year')
endTime = dayjs().endOf('year')
break
case TimeRangeTypeEnum.DAY30:
default:
beginTime = dayjs().subtract(30, 'day').startOf('d')
endTime = dayjs().endOf('d')
break
}
//
await getOrderCountTrendComparison(beginTime, endTime)
}
/** 查询订单数量趋势对照数据 */
const getOrderCountTrendComparison = async (
beginTime: dayjs.ConfigType,
endTime: dayjs.ConfigType
) => {
loading.value = true
//
const list = await TradeStatisticsApi.getOrderCountTrendComparison(
timeRangeType.value,
beginTime,
endTime
)
//
const dates: string[] = []
const series = [...timeRange.get(timeRangeType.value).series]
for (let item of list) {
dates.push(item.value.date)
if (series.length === 2) {
series[0].data.push(fenToYuan(item?.value?.orderPayPrice || 0)) //
series[1].data.push(item?.value?.orderPayCount || 0) //
} else {
series[0].data.push(fenToYuan(item?.reference?.orderPayPrice || 0)) //
series[1].data.push(fenToYuan(item?.value?.orderPayPrice || 0)) //
series[2].data.push(item?.reference?.orderPayCount || 0) //
series[3].data.push(item?.value?.orderPayCount || 0) //
}
}
eChartOptions.xAxis!['data'] = dates
eChartOptions.series = series
// legend424
eChartOptions.legend['data'] = series.map((item) => item.name)
loading.value = false
}
/** 初始化 **/
onMounted(() => {
handleTimeRangeTypeChange()
})
</script>

View File

@ -13,6 +13,9 @@
<el-form-item label="公司" prop="company">
<el-input v-model="formData.company" placeholder="请输入公司" />
</el-form-item>
<el-form-item label="公司地址" prop="companyAddress">
<el-input v-model="formData.companyAddress" placeholder="请输入公司地址" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" placeholder="请输入邮箱" />
</el-form-item>
@ -88,6 +91,7 @@ const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({
name: undefined,
company: undefined,
companyAddress: undefined,
email: undefined,
contacts: undefined,
phone: undefined,
@ -101,6 +105,7 @@ const formData = ref({
const formRules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
company: [{ required: true, message: '公司不能为空', trigger: 'blur' }],
companyAddress: [{ required: true, message: '公司地址不能为空', trigger: 'blur' }],
contacts: [{ required: true, message: '联系人不能为空', trigger: 'blur' }],
phone: [{ required: true, message: '联系人手机号不能为空', trigger: 'blur' },
{
@ -182,6 +187,7 @@ const resetForm = () => {
formData.value = {
name: undefined,
company: undefined,
companyAddress: undefined,
email: undefined,
contacts: undefined,
phone: undefined,

View File

@ -0,0 +1,176 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="合约编号" prop="billno">
<el-input v-model="formData.billno" placeholder="请输入合约编号" />
</el-form-item>
<el-form-item label="客户id" prop="customerId">
<el-input v-model="formData.customerId" placeholder="请输入客户id" />
</el-form-item>
<el-form-item label="客户名称" prop="customerName">
<el-input v-model="formData.customerName" placeholder="请输入客户名称" />
</el-form-item>
<el-form-item label="客户采购编号" prop="customerBuyNo">
<el-input v-model="formData.customerBuyNo" placeholder="请输入客户采购编号" />
</el-form-item>
<el-form-item label="业务日期" prop="bizdate">
<el-date-picker
v-model="formData.bizdate"
type="date"
value-format="x"
placeholder="选择业务日期"
/>
</el-form-item>
<el-form-item label="甲方" prop="partyA">
<el-input v-model="formData.partyA" placeholder="请输入甲方" />
</el-form-item>
<el-form-item label="乙方" prop="partyB">
<el-input v-model="formData.partyB" placeholder="请输入乙方" />
</el-form-item>
<el-form-item label="负责人" prop="head">
<el-input v-model="formData.head" placeholder="请输入负责人" />
</el-form-item>
<el-form-item label="职员" prop="clerk">
<el-input v-model="formData.clerk" placeholder="请输入职员" />
</el-form-item>
<el-form-item label="手机" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入手机" />
</el-form-item>
<el-form-item label="传真" prop="fax">
<el-input v-model="formData.fax" placeholder="请输入传真" />
</el-form-item>
<el-form-item label="金额" prop="amount">
<el-input v-model="formData.amount" placeholder="请输入金额" />
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="formData.address" placeholder="请输入地址" />
</el-form-item>
</el-form>
<!-- 子表的表单 -->
<el-tabs v-model="subTabsName">
<el-tab-pane label="OMS销售合约分录" name="saleContractEntry">
<SaleContractEntryForm ref="saleContractEntryFormRef" :parent-id="formData.id" />
</el-tab-pane>
</el-tabs>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { SaleContractApi, SaleContractVO } from '@/api/oms/salecontract'
import SaleContractEntryForm from './components/SaleContractEntryForm.vue'
/** OMS销售合约 表单 */
defineOptions({ name: 'SaleContractForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
billno: undefined,
customerId: undefined,
customerName: undefined,
customerBuyNo: undefined,
bizdate: undefined,
partyA: undefined,
partyB: undefined,
head: undefined,
clerk: undefined,
phone: undefined,
fax: undefined,
amount: undefined,
address: undefined,
})
const formRules = reactive({
})
const formRef = ref() // Ref
/** 子表的表单 */
const subTabsName = ref('saleContractEntry')
const saleContractEntryFormRef = ref()
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await SaleContractApi.getSaleContract(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
try {
await saleContractEntryFormRef.value.validate()
} catch (e) {
subTabsName.value = 'saleContractEntry'
return
}
//
formLoading.value = true
try {
const data = formData.value as unknown as SaleContractVO
//
data.saleContractEntrys = saleContractEntryFormRef.value.getData()
if (formType.value === 'create') {
await SaleContractApi.createSaleContract(data)
message.success(t('common.createSuccess'))
} else {
await SaleContractApi.updateSaleContract(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
billno: undefined,
customerId: undefined,
customerName: undefined,
customerBuyNo: undefined,
bizdate: undefined,
partyA: undefined,
partyB: undefined,
head: undefined,
clerk: undefined,
phone: undefined,
fax: undefined,
amount: undefined,
address: undefined,
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,150 @@
<template>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
v-loading="formLoading"
label-width="0px"
:inline-message="true"
>
<el-table :data="formData" class="-mt-10px">
<el-table-column label="序号" type="index" width="100" />
<el-table-column label="产品ID" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!">
<el-input v-model="row.productId" placeholder="请输入产品ID" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="产品编码" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productNumber`" :rules="formRules.productNumber" class="mb-0px!">
<el-input v-model="row.productNumber" placeholder="请输入产品编码" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="产品名称" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productName`" :rules="formRules.productName" class="mb-0px!">
<el-input v-model="row.productName" placeholder="请输入产品名称" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="数量" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.qty`" :rules="formRules.qty" class="mb-0px!">
<el-input v-model="row.qty" placeholder="请输入数量" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="单位订价" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.price`" :rules="formRules.price" class="mb-0px!">
<el-input v-model="row.price" placeholder="请输入单位订价" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="折扣" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.discount`" :rules="formRules.discount" class="mb-0px!">
<el-input v-model="row.discount" placeholder="请输入折扣" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="金额" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.amount`" :rules="formRules.amount" class="mb-0px!">
<el-input v-model="row.amount" placeholder="请输入金额" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="交货日期" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.deliveryDate`" :rules="formRules.deliveryDate" class="mb-0px!">
<el-date-picker
v-model="row.deliveryDate"
type="date"
value-format="x"
placeholder="选择交货日期"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="60">
<template #default="{ $index }">
<el-button @click="handleDelete($index)" link></el-button>
</template>
</el-table-column>
</el-table>
</el-form>
<el-row justify="center" class="mt-3">
<el-button @click="handleAdd" round>+ 添加OMS销售合约分录</el-button>
</el-row>
</template>
<script setup lang="ts">
import { SaleContractApi } from '@/api/oms/salecontract'
const props = defineProps<{
parentId: undefined // ID
}>()
const formLoading = ref(false) //
const formData = ref([])
const formRules = reactive({
})
const formRef = ref() // Ref
/** 监听主表的关联字段的变化,加载对应的子表数据 */
watch(
() => props.parentId,
async (val) => {
// 1.
formData.value = []
// 2. val
if (!val) {
return;
}
try {
formLoading.value = true
formData.value = await SaleContractApi.getSaleContractEntryListByParentId(val)
} finally {
formLoading.value = false
}
},
{ immediate: true }
)
/** 新增按钮操作 */
const handleAdd = () => {
const row = {
id: undefined,
parentId: undefined,
productId: undefined,
productNumber: undefined,
productName: undefined,
qty: undefined,
price: undefined,
discount: undefined,
amount: undefined,
deliveryDate: undefined,
}
row.parentId = props.parentId
formData.value.push(row)
}
/** 删除按钮操作 */
const handleDelete = (index) => {
formData.value.splice(index, 1)
}
/** 表单校验 */
const validate = () => {
return formRef.value.validate()
}
/** 表单值 */
const getData = () => {
return formData.value
}
defineExpose({ validate, getData })
</script>

View File

@ -0,0 +1,316 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="合约编号" prop="billno">
<el-input
v-model="queryParams.billno"
placeholder="请输入合约编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="客户名称" prop="customerName">
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="客户采购编号" prop="customerBuyNo">
<el-input
v-model="queryParams.customerBuyNo"
placeholder="请输入客户采购编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="业务日期" prop="bizdate">
<el-date-picker
v-model="queryParams.bizdate"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="甲方" prop="partyA">
<el-input
v-model="queryParams.partyA"
placeholder="请输入甲方"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="乙方" prop="partyB">
<el-input
v-model="queryParams.partyB"
placeholder="请输入乙方"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="负责人" prop="head">
<el-input
v-model="queryParams.head"
placeholder="请输入负责人"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="职员" prop="clerk">
<el-input
v-model="queryParams.clerk"
placeholder="请输入职员"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="手机" prop="phone">
<el-input
v-model="queryParams.phone"
placeholder="请输入手机"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="传真" prop="fax">
<el-input
v-model="queryParams.fax"
placeholder="请输入传真"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="金额" prop="amount">
<el-input
v-model="queryParams.amount"
placeholder="请输入金额"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input
v-model="queryParams.address"
placeholder="请输入地址"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['oms:sale-contract:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['oms:sale-contract:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="合约编号" align="center" prop="billno" />
<el-table-column label="客户id" align="center" prop="customerId" />
<el-table-column label="客户名称" align="center" prop="customerName" />
<el-table-column label="客户采购编号" align="center" prop="customerBuyNo" />
<el-table-column
label="业务日期"
align="center"
prop="bizdate"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="甲方" align="center" prop="partyA" />
<el-table-column label="乙方" align="center" prop="partyB" />
<el-table-column label="负责人" align="center" prop="head" />
<el-table-column label="职员" align="center" prop="clerk" />
<el-table-column label="手机" align="center" prop="phone" />
<el-table-column label="传真" align="center" prop="fax" />
<el-table-column label="金额" align="center" prop="amount" />
<el-table-column label="地址" align="center" prop="address" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['oms:sale-contract:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['oms:sale-contract:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<SaleContractForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { SaleContractApi, SaleContractVO } from '@/api/oms/salecontract'
import SaleContractForm from './SaleContractForm.vue'
/** OMS销售合约 列表 */
defineOptions({ name: 'SaleContract' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<SaleContractVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
billno: undefined,
customerName: undefined,
customerBuyNo: undefined,
bizdate: [],
partyA: undefined,
partyB: undefined,
head: undefined,
clerk: undefined,
phone: undefined,
fax: undefined,
amount: undefined,
address: undefined,
createTime: [],
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await SaleContractApi.getSaleContractPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await SaleContractApi.deleteSaleContract(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await SaleContractApi.exportSaleContract(queryParams)
download.excel(data, 'OMS销售合约.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -118,21 +118,29 @@
<el-button
type="danger"
plain
@click="handleReject(selectionList.map((item) => item.id))"
@click="handleReject()"
:disabled="selectionList.length === 0"
>驳回
</el-button>
<el-button
type="primary"
plain
@click="handleUpdateBillStatus('invalid')"
:disabled="selectionList.length === 0"
>
作废
</el-button>
<el-button
type="warning"
plain
@click="handleUpdateBillStatus(selectionList.map((item) => item.id),'submit')"
@click="handleUpdateBillStatus('submit')"
:disabled="selectionList.length === 0"
>提交
</el-button>
<el-button
type="primary"
plain
@click="handleUpdateBillStatus(selectionList.map((item) => item.id),'audit')"
@click="handleUpdateBillStatus('audit')"
:disabled="selectionList.length === 0"
>
审核
@ -140,15 +148,7 @@
<el-button
type="primary"
plain
@click="handleUpdateBillStatus(selectionList.map((item) => item.id),'unaudit')"
:disabled="selectionList.length === 0"
>
反审核
</el-button>
<el-button
type="primary"
plain
@click="generateProduceOrder(selectionList.map((item) => item.id))"
@click="generateProduceOrder()"
:disabled="selectionList.length === 0 || queryParams.tabType === 0"
>
生成制单
@ -337,8 +337,6 @@ const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) //
//
const rejectOpen = ref(false)
const rejectTitle = ref('')
@ -402,22 +400,40 @@ const handleDelete = async (id: number) => {
/** 驳回操作 */
const handleReject = async (ids: number[]) => {
const handleReject = async () => {
const ids = selectionList.value.map((item) => item.id)
const billStatus = selectionList.value.map((item) => item.billStatus)
for(let vals of billStatus) {
if(vals!='B'){
message.error("请选择单据状态已提交的数据行");
return;
}
}
rejectform.rejectReason = undefined
rejectTitle.value = "是否驳回选中的数据项?"
rejectOpen.value = true
}
/** 审批/反审批操作 */
const handleUpdateBillStatus = async (ids: number[], operateKey: string) => {
/** 审批操作 */
const handleUpdateBillStatus = async (operateKey: string) => {
try {
const ids = selectionList.value.map((item) => item.id)
const billStatus = selectionList.value.map((item) => item.billStatus)
for(let vals of billStatus) {
if("audit" == operateKey && vals!='B'){
message.error("请选择单据状态已提交的数据行");
return;
}
if("invalid" == operateKey && vals!='B'){
message.error("请选择单据状态已提交的数据行");
return;
}
}
//
let operateName = ''
if(operateKey === 'submit'){
operateName = '提交'
}else if(operateKey === "audit"){
operateName = '审核'
}else if(operateKey === "unaudit"){
operateName = '反审核'
}else {
return
}
@ -433,8 +449,17 @@ const handleUpdateBillStatus = async (ids: number[], operateKey: string) => {
/** 审批/反审批操作 */
const generateProduceOrder = async (ids: number[]) => {
const generateProduceOrder = async () => {
try {
const ids = selectionList.value.map((item) => item.id)
const billStatus = selectionList.value.map((item) => item.billStatus)
for(let vals of billStatus) {
if(vals!='C'){
message.error("请选择单据状态已审核的数据行");
return;
}
}
//
await message.confirm(`是否生成生产制单?`)
//
@ -480,6 +505,9 @@ const handleSelectionChange = (rows: SaleOrderVO[]) => {
/** 驳回提交按钮 */
const submitRejectForm = async () => {
const ids = selectionList.value.map((item) => item.id)
const billStatus = selectionList.value.map((item) => item.billStatus)
if(rejectform.rejectReason != null) {
const data = {
ids:ids,