Merge remote-tracking branch 'origin/feat/erp-purchase-plan' into 0.8.X
This commit is contained in:
@@ -92,6 +92,62 @@ CREATE TABLE IF NOT EXISTS `erp_purchase_plan_delivery` (
|
||||
KEY `idx_coil_no` (`coil_no`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='采购计划到货明细';
|
||||
|
||||
-- 5. 到货上传批次(每上传一份到货表格记一条,便于随时回看每一次上传)
|
||||
CREATE TABLE IF NOT EXISTS `erp_purchase_plan_delivery_batch` (
|
||||
`batch_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '批次ID',
|
||||
`plan_id` BIGINT NOT NULL COMMENT '关联计划ID',
|
||||
`file_name` VARCHAR(255) DEFAULT NULL COMMENT '文件名',
|
||||
`row_count` INT DEFAULT 0 COMMENT '本次到货行数',
|
||||
`matched_count` INT DEFAULT 0 COMMENT '本次匹配上明细卷号的卷数',
|
||||
`arrived_percent` DECIMAL(6,2) DEFAULT 0 COMMENT '上传后到货百分比快照',
|
||||
`del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标志',
|
||||
`create_by` VARCHAR(64) DEFAULT NULL,
|
||||
`create_time` DATETIME DEFAULT NULL,
|
||||
`update_by` VARCHAR(64) DEFAULT NULL,
|
||||
`update_time` DATETIME DEFAULT NULL,
|
||||
`remark` VARCHAR(512) DEFAULT NULL,
|
||||
PRIMARY KEY (`batch_id`),
|
||||
KEY `idx_plan_id` (`plan_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='采购计划到货上传批次';
|
||||
|
||||
-- 6. 供应商往来流水台账
|
||||
CREATE TABLE IF NOT EXISTS `erp_supplier_transaction` (
|
||||
`txn_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '往来流水ID',
|
||||
`supplier_id` BIGINT NOT NULL COMMENT '关联供应商ID',
|
||||
`txn_date` DATE DEFAULT NULL COMMENT '发生日期',
|
||||
`txn_type` CHAR(1) NOT NULL DEFAULT '1' COMMENT '类型: 1-采购应付 2-付款 3-退货 4-其他',
|
||||
`amount` DECIMAL(16,2) NOT NULL DEFAULT 0 COMMENT '金额',
|
||||
`doc_no` VARCHAR(64) DEFAULT NULL COMMENT '单据号',
|
||||
`del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标志',
|
||||
`create_by` VARCHAR(64) DEFAULT NULL,
|
||||
`create_time` DATETIME DEFAULT NULL,
|
||||
`update_by` VARCHAR(64) DEFAULT NULL,
|
||||
`update_time` DATETIME DEFAULT NULL,
|
||||
`remark` VARCHAR(512) DEFAULT NULL,
|
||||
PRIMARY KEY (`txn_id`),
|
||||
KEY `idx_supplier_id` (`supplier_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商往来流水台账';
|
||||
|
||||
-- ====== 既有表增列(幂等:列不存在才加) ======
|
||||
SET @c := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'erp_purchase_plan_delivery' AND COLUMN_NAME = 'batch_id');
|
||||
SET @s := IF(@c = 0, 'ALTER TABLE erp_purchase_plan_delivery ADD COLUMN batch_id BIGINT NULL COMMENT ''关联上传批次ID'' AFTER plan_id', 'SELECT 1');
|
||||
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 到货行是否 WMS 钢卷表已确认到货(supplier_coil_no 存在且 data_type<>10)
|
||||
SET @c := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'erp_purchase_plan_delivery' AND COLUMN_NAME = 'arrived');
|
||||
SET @s := IF(@c = 0, 'ALTER TABLE erp_purchase_plan_delivery ADD COLUMN arrived TINYINT NOT NULL DEFAULT 0 COMMENT ''是否WMS确认到货:1是0否'' AFTER batch_id', 'SELECT 1');
|
||||
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 计划要求总数量(卷/件) = Σ明细数量
|
||||
SET @c := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'erp_purchase_plan' AND COLUMN_NAME = 'plan_qty');
|
||||
SET @s := IF(@c = 0, 'ALTER TABLE erp_purchase_plan ADD COLUMN plan_qty INT NOT NULL DEFAULT 0 COMMENT ''计划要求总数量(卷/件)'' AFTER arrived_weight', 'SELECT 1');
|
||||
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 已到货卷数 = WMS确认到货的上传卷数
|
||||
SET @c := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'erp_purchase_plan' AND COLUMN_NAME = 'arrived_count');
|
||||
SET @s := IF(@c = 0, 'ALTER TABLE erp_purchase_plan ADD COLUMN arrived_count INT NOT NULL DEFAULT 0 COMMENT ''已到货卷数'' AFTER plan_qty', 'SELECT 1');
|
||||
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
|
||||
-- ============ 菜单 ============
|
||||
-- 采购目录挂在「生产辅助」(path=helper) 下;父 id 按 path 动态解析,兼容不同环境的不同 menu_id。
|
||||
@@ -126,6 +182,6 @@ REPLACE INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component
|
||||
(2100000000000000010, '采购审核', @purchase_id, 3, 'purchaseAudit', 'erp/purchaseAudit/index', '', 1, 0, 'C', '0', '0', 'erp:purchasePlan:auditList', 'validCode', 'admin', sysdate(), '采购审核菜单'),
|
||||
(2100000000000000011, '审核操作', 2100000000000000010, 1, '', '', '', 1, 0, 'F', '0', '0', 'erp:purchasePlan:audit', '#', 'admin', sysdate(), '');
|
||||
|
||||
-- 7) 采购进度(计划级到货进度总览)
|
||||
-- 7) 到货记录(审核通过的计划上传到货表格、按卷号比对、到货百分比)
|
||||
REPLACE INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) VALUES
|
||||
(2100000000000000020, '采购进度', @purchase_id, 4, 'purchaseProgress', 'erp/purchaseProgress/index', '', 1, 0, 'C', '0', '0', 'erp:purchasePlan:list', 'data-line', 'admin', sysdate(), '采购进度总览');
|
||||
(2100000000000000020, '到货记录', @purchase_id, 4, 'purchaseDelivery', 'erp/purchaseDelivery/index', '', 1, 0, 'C', '0', '0', 'erp:purchasePlan:list', 'truck', 'admin', sysdate(), '到货记录与卷号比对');
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.klp.common.utils.poi.ExcelUtil;
|
||||
import com.klp.erp.domain.bo.ErpPurchasePlanAuditBo;
|
||||
import com.klp.erp.domain.bo.ErpPurchasePlanBo;
|
||||
import com.klp.erp.domain.vo.ErpContractOptionVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanDeliveryBatchVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanDeliveryVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanItemVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanVo;
|
||||
@@ -131,7 +132,7 @@ public class ErpPurchasePlanController extends BaseController {
|
||||
@PostMapping(value = "/{planId}/importDelivery", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public R<Map<String, Object>> importDelivery(@PathVariable Long planId,
|
||||
@RequestPart("file") MultipartFile file) throws Exception {
|
||||
Map<String, Object> result = iErpPurchasePlanService.importDelivery(planId, file.getInputStream(), getUsername());
|
||||
Map<String, Object> result = iErpPurchasePlanService.importDelivery(planId, file.getInputStream(), file.getOriginalFilename(), getUsername());
|
||||
return R.ok(String.valueOf(result.get("message")), result);
|
||||
}
|
||||
|
||||
@@ -141,6 +142,25 @@ public class ErpPurchasePlanController extends BaseController {
|
||||
return R.ok(iErpPurchasePlanService.queryDeliveryList(planId));
|
||||
}
|
||||
|
||||
/** 某计划的到货上传批次(每次上传一条,可回看) */
|
||||
@GetMapping("/{planId}/deliveryBatches")
|
||||
public R<List<ErpPurchasePlanDeliveryBatchVo>> deliveryBatches(@PathVariable Long planId) {
|
||||
return R.ok(iErpPurchasePlanService.queryDeliveryBatches(planId));
|
||||
}
|
||||
|
||||
/** 某批次的到货明细 */
|
||||
@GetMapping("/deliveryBatch/{batchId}")
|
||||
public R<List<ErpPurchasePlanDeliveryVo>> deliveryByBatch(@PathVariable Long batchId) {
|
||||
return R.ok(iErpPurchasePlanService.queryDeliveryListByBatch(batchId));
|
||||
}
|
||||
|
||||
/** 到货记录页左侧:审核通过的计划分页 */
|
||||
@GetMapping("/deliveryPlans")
|
||||
public TableDataInfo<ErpPurchasePlanVo> deliveryPlans(@RequestParam(value = "keyword", required = false) String keyword,
|
||||
PageQuery pageQuery) {
|
||||
return iErpPurchasePlanService.queryDeliveryPlanPage(keyword, pageQuery);
|
||||
}
|
||||
|
||||
/** 删除到货明细 */
|
||||
@Log(title = "采购计划-到货", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/delivery/{deliveryId}")
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.klp.erp.controller;
|
||||
|
||||
import com.klp.common.annotation.Log;
|
||||
import com.klp.common.annotation.RepeatSubmit;
|
||||
import com.klp.common.core.controller.BaseController;
|
||||
import com.klp.common.core.domain.R;
|
||||
import com.klp.common.core.validate.AddGroup;
|
||||
import com.klp.common.core.validate.EditGroup;
|
||||
import com.klp.common.enums.BusinessType;
|
||||
import com.klp.erp.domain.bo.ErpSupplierTransactionBo;
|
||||
import com.klp.erp.domain.vo.ErpSupplierTransactionVo;
|
||||
import com.klp.erp.service.IErpSupplierTransactionService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 供应商往来流水
|
||||
*
|
||||
* @author klp
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/erp/supplierTxn")
|
||||
public class ErpSupplierTransactionController extends BaseController {
|
||||
|
||||
private final IErpSupplierTransactionService txnService;
|
||||
|
||||
/** 某供应商的往来流水(含逐笔余额) */
|
||||
@GetMapping("/list")
|
||||
public R<List<ErpSupplierTransactionVo>> list(@RequestParam("supplierId") Long supplierId) {
|
||||
return R.ok(txnService.queryBySupplier(supplierId));
|
||||
}
|
||||
|
||||
/** 某供应商的往来汇总 */
|
||||
@GetMapping("/summary")
|
||||
public R<Map<String, Object>> summary(@RequestParam("supplierId") Long supplierId) {
|
||||
return R.ok(txnService.summary(supplierId));
|
||||
}
|
||||
|
||||
@Log(title = "供应商往来", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit
|
||||
@PostMapping
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody ErpSupplierTransactionBo bo) {
|
||||
return toAjax(txnService.insertByBo(bo));
|
||||
}
|
||||
|
||||
@Log(title = "供应商往来", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit
|
||||
@PutMapping
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ErpSupplierTransactionBo bo) {
|
||||
return toAjax(txnService.updateByBo(bo));
|
||||
}
|
||||
|
||||
@Log(title = "供应商往来", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{txnIds}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] txnIds) {
|
||||
return toAjax(txnService.deleteByIds(Arrays.asList(txnIds)));
|
||||
}
|
||||
}
|
||||
@@ -54,9 +54,15 @@ public class ErpPurchasePlan extends BaseEntity {
|
||||
/** 计划总重量(T) */
|
||||
private BigDecimal planWeight;
|
||||
|
||||
/** 已到货重量(T) */
|
||||
/** 已到货重量(T) = Σ到货卷(WMS已确认)单卷重量 */
|
||||
private BigDecimal arrivedWeight;
|
||||
|
||||
/** 计划要求总数量(卷/件) = Σ明细数量 */
|
||||
private Integer planQty;
|
||||
|
||||
/** 已到货卷数 = WMS确认到货的上传卷数 */
|
||||
private Integer arrivedCount;
|
||||
|
||||
/** 删除标志 */
|
||||
@TableLogic
|
||||
private String delFlag;
|
||||
|
||||
@@ -30,6 +30,12 @@ public class ErpPurchasePlanDelivery extends BaseEntity {
|
||||
/** 关联计划ID */
|
||||
private Long planId;
|
||||
|
||||
/** 关联上传批次ID */
|
||||
private Long batchId;
|
||||
|
||||
/** 是否WMS已确认到货: 1-是 0-否(卷号在钢卷表查不到或为占位类型) */
|
||||
private Integer arrived;
|
||||
|
||||
/** 日期 */
|
||||
private Date arrivalDate;
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.klp.erp.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.klp.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 到货上传批次对象 erp_purchase_plan_delivery_batch
|
||||
* 每上传一份到货 Excel 记一条批次,便于随时回看每一次上传。
|
||||
*
|
||||
* @author klp
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("erp_purchase_plan_delivery_batch")
|
||||
public class ErpPurchasePlanDeliveryBatch extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 批次ID */
|
||||
@TableId(value = "batch_id")
|
||||
private Long batchId;
|
||||
|
||||
/** 关联计划ID */
|
||||
private Long planId;
|
||||
|
||||
/** 文件名 */
|
||||
private String fileName;
|
||||
|
||||
/** 本次导入到货行数 */
|
||||
private Integer rowCount;
|
||||
|
||||
/** 本次与明细卷号匹配上的卷数 */
|
||||
private Integer matchedCount;
|
||||
|
||||
/** 上传后计划到货百分比快照(0-100) */
|
||||
private BigDecimal arrivedPercent;
|
||||
|
||||
/** 删除标志 */
|
||||
@TableLogic
|
||||
private String delFlag;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.klp.erp.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.klp.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 供应商往来流水对象 erp_supplier_transaction
|
||||
*
|
||||
* @author klp
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("erp_supplier_transaction")
|
||||
public class ErpSupplierTransaction extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 往来流水ID */
|
||||
@TableId(value = "txn_id")
|
||||
private Long txnId;
|
||||
|
||||
/** 关联供应商ID */
|
||||
private Long supplierId;
|
||||
|
||||
/** 发生日期 */
|
||||
private Date txnDate;
|
||||
|
||||
/** 类型: 1-采购应付 2-付款 3-退货 4-其他 */
|
||||
private String txnType;
|
||||
|
||||
/** 金额 */
|
||||
private BigDecimal amount;
|
||||
|
||||
/** 单据号 */
|
||||
private String docNo;
|
||||
|
||||
/** 删除标志 */
|
||||
@TableLogic
|
||||
private String delFlag;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.klp.erp.domain.bo;
|
||||
|
||||
import com.klp.common.core.domain.BaseEntity;
|
||||
import com.klp.common.core.validate.AddGroup;
|
||||
import com.klp.common.core.validate.EditGroup;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 供应商往来流水业务对象
|
||||
*
|
||||
* @author klp
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ErpSupplierTransactionBo extends BaseEntity {
|
||||
|
||||
@NotNull(message = "主键不能为空", groups = {EditGroup.class})
|
||||
private Long txnId;
|
||||
|
||||
@NotNull(message = "供应商不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||
private Long supplierId;
|
||||
|
||||
private Date txnDate;
|
||||
|
||||
@NotNull(message = "类型不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||
private String txnType;
|
||||
|
||||
@NotNull(message = "金额不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||
private BigDecimal amount;
|
||||
|
||||
private String docNo;
|
||||
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.klp.erp.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 到货上传批次视图对象
|
||||
*
|
||||
* @author klp
|
||||
*/
|
||||
@Data
|
||||
public class ErpPurchasePlanDeliveryBatchVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long batchId;
|
||||
|
||||
private Long planId;
|
||||
|
||||
private String fileName;
|
||||
|
||||
private Integer rowCount;
|
||||
|
||||
private Integer matchedCount;
|
||||
|
||||
private BigDecimal arrivedPercent;
|
||||
|
||||
private String createBy;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
private String remark;
|
||||
}
|
||||
@@ -26,6 +26,11 @@ public class ErpPurchasePlanDeliveryVo implements Serializable {
|
||||
|
||||
private Long planId;
|
||||
|
||||
private Long batchId;
|
||||
|
||||
/** 是否WMS已确认到货: 1-是 0-否 */
|
||||
private Integer arrived;
|
||||
|
||||
@ExcelProperty(value = "日期")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date arrivalDate;
|
||||
|
||||
@@ -59,12 +59,21 @@ public class ErpPurchasePlanVo implements Serializable {
|
||||
@ExcelProperty(value = "已到货重量(T)")
|
||||
private BigDecimal arrivedWeight;
|
||||
|
||||
/** 计划要求总数量(卷/件) */
|
||||
private Integer planQty;
|
||||
|
||||
/** 已到货卷数 */
|
||||
private Integer arrivedCount;
|
||||
|
||||
@ExcelProperty(value = "备注")
|
||||
private String remark;
|
||||
|
||||
/** 到货进度百分比(0-100) */
|
||||
/** 到货进度-按重量(0-100) = 已到货重量/计划要求总重量 */
|
||||
private BigDecimal progress;
|
||||
|
||||
/** 到货进度-按卷数(0-100) = 已到货卷数/计划要求总数量 */
|
||||
private BigDecimal progressQty;
|
||||
|
||||
/** 明细行 */
|
||||
private List<ErpPurchasePlanItemVo> items;
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.klp.erp.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 供应商往来流水视图对象
|
||||
*
|
||||
* @author klp
|
||||
*/
|
||||
@Data
|
||||
public class ErpSupplierTransactionVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long txnId;
|
||||
|
||||
private Long supplierId;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date txnDate;
|
||||
|
||||
/** 类型: 1-采购应付 2-付款 3-退货 4-其他 */
|
||||
private String txnType;
|
||||
|
||||
private BigDecimal amount;
|
||||
|
||||
private String docNo;
|
||||
|
||||
private String createBy;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
private String remark;
|
||||
|
||||
/** 计算字段:截至该笔的应付余额(正数=欠供应商) */
|
||||
private BigDecimal balance;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.klp.erp.mapper;
|
||||
|
||||
import com.klp.common.core.mapper.BaseMapperPlus;
|
||||
import com.klp.erp.domain.ErpPurchasePlanDeliveryBatch;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanDeliveryBatchVo;
|
||||
|
||||
/**
|
||||
* 到货上传批次Mapper接口
|
||||
*
|
||||
* @author klp
|
||||
*/
|
||||
public interface ErpPurchasePlanDeliveryBatchMapper extends BaseMapperPlus<ErpPurchasePlanDeliveryBatchMapper, ErpPurchasePlanDeliveryBatch, ErpPurchasePlanDeliveryBatchVo> {
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import com.klp.erp.domain.vo.ErpPurchasePlanItemVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanVo;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -44,4 +45,10 @@ public interface ErpPurchasePlanMapper extends BaseMapperPlus<ErpPurchasePlanMap
|
||||
* 按合同关键字(订单编号/合同号/合同名称)查出关联的采购计划ID,用于综合搜索。
|
||||
*/
|
||||
List<Long> selectPlanIdsByContractKeyword(@Param("kw") String kw);
|
||||
|
||||
/**
|
||||
* 在 WMS 钢卷表(wms_material_coil)中查出已存在且非占位(data_type<>10)的厂家卷号集合,
|
||||
* 用于判定上传到货行的卷号是否已实际到货入库。
|
||||
*/
|
||||
List<String> selectExistingSupplierCoilNos(@Param("coilNos") Collection<String> coilNos);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.klp.erp.mapper;
|
||||
|
||||
import com.klp.common.core.mapper.BaseMapperPlus;
|
||||
import com.klp.erp.domain.ErpSupplierTransaction;
|
||||
import com.klp.erp.domain.vo.ErpSupplierTransactionVo;
|
||||
|
||||
/**
|
||||
* 供应商往来流水Mapper接口
|
||||
*
|
||||
* @author klp
|
||||
*/
|
||||
public interface ErpSupplierTransactionMapper extends BaseMapperPlus<ErpSupplierTransactionMapper, ErpSupplierTransaction, ErpSupplierTransactionVo> {
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.klp.erp.domain.bo.ErpPurchasePlanAuditBo;
|
||||
import com.klp.erp.domain.bo.ErpPurchasePlanBo;
|
||||
import com.klp.erp.domain.vo.ErpContractOptionVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanAuditLogVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanDeliveryBatchVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanDeliveryVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanItemVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanVo;
|
||||
@@ -63,11 +64,20 @@ public interface IErpPurchasePlanService {
|
||||
* 导入到货 Excel:校验列/数值、kg→t 单位纠正,写入并刷新进度归档。
|
||||
* 返回 {count: 入库条数, message: 回执文案, kgConverted: 是否做了单位换算}
|
||||
*/
|
||||
Map<String, Object> importDelivery(Long planId, InputStream inputStream, String operator);
|
||||
Map<String, Object> importDelivery(Long planId, InputStream inputStream, String fileName, String operator);
|
||||
|
||||
/** 查询某计划的到货明细 */
|
||||
/** 查询某计划的到货明细(matched 标记是否与明细卷号匹配) */
|
||||
List<ErpPurchasePlanDeliveryVo> queryDeliveryList(Long planId);
|
||||
|
||||
/** 某计划下指定批次的到货明细 */
|
||||
List<ErpPurchasePlanDeliveryVo> queryDeliveryListByBatch(Long batchId);
|
||||
|
||||
/** 某计划的到货上传批次(最新在前) */
|
||||
List<ErpPurchasePlanDeliveryBatchVo> queryDeliveryBatches(Long planId);
|
||||
|
||||
/** 到货记录页左侧:审核通过的计划分页(含合同号、明细数、进度) */
|
||||
TableDataInfo<ErpPurchasePlanVo> queryDeliveryPlanPage(String keyword, PageQuery pageQuery);
|
||||
|
||||
/** 删除到货明细,并刷新进度 */
|
||||
Boolean deleteDelivery(Long deliveryId);
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.klp.erp.service;
|
||||
|
||||
import com.klp.erp.domain.bo.ErpSupplierTransactionBo;
|
||||
import com.klp.erp.domain.vo.ErpSupplierTransactionVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 供应商往来流水Service接口
|
||||
*
|
||||
* @author klp
|
||||
*/
|
||||
public interface IErpSupplierTransactionService {
|
||||
|
||||
/** 某供应商的往来流水(含逐笔累计余额,最新在前) */
|
||||
List<ErpSupplierTransactionVo> queryBySupplier(Long supplierId);
|
||||
|
||||
/** 某供应商的往来汇总:应付合计/付款合计/退货合计/当前余额 */
|
||||
Map<String, Object> summary(Long supplierId);
|
||||
|
||||
Boolean insertByBo(ErpSupplierTransactionBo bo);
|
||||
|
||||
Boolean updateByBo(ErpSupplierTransactionBo bo);
|
||||
|
||||
Boolean deleteByIds(Collection<Long> ids);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import com.klp.common.utils.StringUtils;
|
||||
import com.klp.erp.domain.ErpPurchasePlan;
|
||||
import com.klp.erp.domain.ErpPurchasePlanContractRel;
|
||||
import com.klp.erp.domain.ErpPurchasePlanDelivery;
|
||||
import com.klp.erp.domain.ErpPurchasePlanDeliveryBatch;
|
||||
import com.klp.erp.domain.ErpPurchasePlanItem;
|
||||
import com.klp.erp.domain.ErpPurchasePlanAuditLog;
|
||||
import com.klp.erp.domain.bo.ErpPurchasePlanAuditBo;
|
||||
@@ -20,11 +21,13 @@ import com.klp.erp.domain.bo.ErpPurchasePlanBo;
|
||||
import com.klp.erp.domain.bo.ErpPurchasePlanItemBo;
|
||||
import com.klp.erp.domain.vo.ErpContractOptionVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanAuditLogVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanDeliveryBatchVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanDeliveryImportVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanDeliveryVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanItemVo;
|
||||
import com.klp.erp.domain.vo.ErpPurchasePlanVo;
|
||||
import com.klp.erp.mapper.ErpPurchasePlanContractRelMapper;
|
||||
import com.klp.erp.mapper.ErpPurchasePlanDeliveryBatchMapper;
|
||||
import com.klp.erp.mapper.ErpPurchasePlanDeliveryMapper;
|
||||
import com.klp.erp.mapper.ErpPurchasePlanItemMapper;
|
||||
import com.klp.erp.mapper.ErpPurchasePlanAuditLogMapper;
|
||||
@@ -42,9 +45,10 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -63,14 +67,12 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
private static final String AUDIT_PASS = "1";
|
||||
private static final String AUDIT_REJECT = "2";
|
||||
private static final String AUDIT_DRAFT = "3";
|
||||
private static final String ITEM_NOT_ARRIVED = "0";
|
||||
private static final String ITEM_PARTIAL = "1";
|
||||
private static final String ITEM_ARRIVED = "2";
|
||||
|
||||
private final ErpPurchasePlanMapper baseMapper;
|
||||
private final ErpPurchasePlanItemMapper itemMapper;
|
||||
private final ErpPurchasePlanContractRelMapper relMapper;
|
||||
private final ErpPurchasePlanDeliveryMapper deliveryMapper;
|
||||
private final ErpPurchasePlanDeliveryBatchMapper deliveryBatchMapper;
|
||||
private final ErpPurchasePlanAuditLogMapper auditLogMapper;
|
||||
|
||||
@Override
|
||||
@@ -90,7 +92,7 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
if (!orderIds.isEmpty()) {
|
||||
vo.setContractCodes(baseMapper.selectOrderCodes(orderIds));
|
||||
}
|
||||
vo.setProgress(calcProgress(vo.getArrivedWeight(), vo.getPlanWeight()));
|
||||
fillProgress(vo);
|
||||
// 审核历史(含每次驳回理由)
|
||||
vo.setAuditLogs(queryAuditLogs(planId));
|
||||
return vo;
|
||||
@@ -107,14 +109,14 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
@Override
|
||||
public TableDataInfo<ErpPurchasePlanVo> queryPageList(ErpPurchasePlanBo bo, PageQuery pageQuery) {
|
||||
Page<ErpPurchasePlanVo> result = baseMapper.selectVoPage(pageQuery.build(), buildQueryWrapper(bo));
|
||||
result.getRecords().forEach(v -> v.setProgress(calcProgress(v.getArrivedWeight(), v.getPlanWeight())));
|
||||
result.getRecords().forEach(this::fillProgress);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ErpPurchasePlanVo> queryList(ErpPurchasePlanBo bo) {
|
||||
List<ErpPurchasePlanVo> list = baseMapper.selectVoList(buildQueryWrapper(bo));
|
||||
list.forEach(v -> v.setProgress(calcProgress(v.getArrivedWeight(), v.getPlanWeight())));
|
||||
list.forEach(this::fillProgress);
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -127,7 +129,7 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
@Override
|
||||
public List<ErpPurchasePlanVo> queryPlansByContract(Long orderId) {
|
||||
List<ErpPurchasePlanVo> list = baseMapper.selectPlansByContract(orderId);
|
||||
list.forEach(v -> v.setProgress(calcProgress(v.getArrivedWeight(), v.getPlanWeight())));
|
||||
list.forEach(this::fillProgress);
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -164,6 +166,8 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
add.setAuditStatus(AUDIT_DRAFT); // 新建为「待送审」,需手动送审后才进入审核池
|
||||
add.setArrivedWeight(BigDecimal.ZERO);
|
||||
add.setPlanWeight(sumItemWeight(bo.getItems()));
|
||||
add.setPlanQty(sumItemQty(bo.getItems()));
|
||||
add.setArrivedCount(0);
|
||||
if (baseMapper.insert(add) <= 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -187,6 +191,7 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
update.setPlanNo(bo.getPlanNo());
|
||||
}
|
||||
update.setPlanWeight(sumItemWeight(bo.getItems()));
|
||||
update.setPlanQty(sumItemQty(bo.getItems()));
|
||||
baseMapper.updateById(update);
|
||||
// 覆盖式重写明细与合同关联
|
||||
itemMapper.delete(Wrappers.lambdaQuery(ErpPurchasePlanItem.class)
|
||||
@@ -195,6 +200,8 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
relMapper.delete(Wrappers.lambdaQuery(ErpPurchasePlanContractRel.class)
|
||||
.eq(ErpPurchasePlanContractRel::getPlanId, bo.getPlanId()));
|
||||
saveContractRels(bo.getPlanId(), bo.getOrderIds());
|
||||
// 卷号匹配可能变化,刷新到货进度
|
||||
refreshProgress(bo.getPlanId());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -234,6 +241,13 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
|
||||
private int sumItemQty(List<ErpPurchasePlanItemBo> items) {
|
||||
if (items == null) {
|
||||
return 0;
|
||||
}
|
||||
return items.stream().mapToInt(i -> i.getQuantity() == null ? 0 : i.getQuantity()).sum();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
@@ -298,7 +312,7 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> importDelivery(Long planId, InputStream inputStream, String operator) {
|
||||
public Map<String, Object> importDelivery(Long planId, InputStream inputStream, String fileName, String operator) {
|
||||
ErpPurchasePlan plan = baseMapper.selectById(planId);
|
||||
if (plan == null) {
|
||||
throw new ServiceException("采购计划不存在");
|
||||
@@ -334,11 +348,28 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
.orElse(BigDecimal.ZERO);
|
||||
boolean kgConverted = maxCoil.compareTo(KG_THRESHOLD) > 0;
|
||||
|
||||
// 3) 合并单元格向下填充 + 单位换算 + 落库
|
||||
// 3) 卷号去 WMS 钢卷表校验:存在且 data_type<>10 即视为已实际到货入库
|
||||
List<String> uploadCoilNos = valid.stream()
|
||||
.map(ErpPurchasePlanDeliveryImportVo::getCoilNo)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toList());
|
||||
Set<String> wmsArrivedSet = uploadCoilNos.isEmpty() ? new HashSet<>()
|
||||
: baseMapper.selectExistingSupplierCoilNos(uploadCoilNos).stream()
|
||||
.map(this::normCoil).collect(Collectors.toSet());
|
||||
|
||||
// 4) 先建批次(每次上传存档,可随时回看)
|
||||
ErpPurchasePlanDeliveryBatch batch = new ErpPurchasePlanDeliveryBatch();
|
||||
batch.setPlanId(planId);
|
||||
batch.setFileName(StringUtils.isNotBlank(fileName) ? fileName : "到货表");
|
||||
deliveryBatchMapper.insert(batch);
|
||||
|
||||
// 5) 合并单元格向下填充 + 单位换算 + 落库 + WMS到货标记
|
||||
String lastTruckNo = null;
|
||||
BigDecimal lastTruckWeight = null;
|
||||
Integer lastPieceCount = null;
|
||||
int count = 0;
|
||||
int matched = 0;
|
||||
for (ErpPurchasePlanDeliveryImportVo row : rows) {
|
||||
if (StringUtils.isNotBlank(row.getTruckNo())) {
|
||||
lastTruckNo = row.getTruckNo();
|
||||
@@ -348,9 +379,12 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
if (StringUtils.isBlank(row.getCoilNo()) && row.getCoilWeight() == null) {
|
||||
continue;
|
||||
}
|
||||
boolean arrived = StringUtils.isNotBlank(row.getCoilNo()) && wmsArrivedSet.contains(normCoil(row.getCoilNo()));
|
||||
BigDecimal truckWeight = row.getTruckWeight() != null ? row.getTruckWeight() : lastTruckWeight;
|
||||
ErpPurchasePlanDelivery d = new ErpPurchasePlanDelivery();
|
||||
d.setPlanId(planId);
|
||||
d.setBatchId(batch.getBatchId());
|
||||
d.setArrived(arrived ? 1 : 0);
|
||||
d.setArrivalDate(parseDate(row.getArrivalDate()));
|
||||
d.setGrade(row.getGrade());
|
||||
d.setSpec(row.getSpec());
|
||||
@@ -363,17 +397,29 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
d.setArrivalStation(row.getArrivalStation());
|
||||
deliveryMapper.insert(d);
|
||||
count++;
|
||||
if (arrived) {
|
||||
matched++;
|
||||
}
|
||||
}
|
||||
// 6) 汇总刷新进度
|
||||
refreshProgress(planId);
|
||||
|
||||
StringBuilder msg = new StringBuilder("成功导入 " + count + " 条到货记录");
|
||||
// 7) 回填批次统计(行数、WMS确认到货卷数、上传后进度快照)
|
||||
ErpPurchasePlan after = baseMapper.selectById(planId);
|
||||
batch.setRowCount(count);
|
||||
batch.setMatchedCount(matched);
|
||||
batch.setArrivedPercent(calcPct(after.getArrivedWeight(), after.getPlanWeight()));
|
||||
deliveryBatchMapper.updateById(batch);
|
||||
|
||||
StringBuilder msg = new StringBuilder("成功导入 " + count + " 条到货记录,其中 " + matched + " 卷在钢卷表确认到货");
|
||||
if (kgConverted) {
|
||||
msg.append(";检测到重量疑似按 kg 录入(最大单卷 ")
|
||||
.append(maxCoil.stripTrailingZeros().toPlainString())
|
||||
.append("),已自动 ÷1000 换算为吨");
|
||||
}
|
||||
Map<String, Object> result = new HashMap<>(4);
|
||||
Map<String, Object> result = new HashMap<>(6);
|
||||
result.put("count", count);
|
||||
result.put("matched", matched);
|
||||
result.put("kgConverted", kgConverted);
|
||||
result.put("message", msg.toString());
|
||||
return result;
|
||||
@@ -391,7 +437,40 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
public List<ErpPurchasePlanDeliveryVo> queryDeliveryList(Long planId) {
|
||||
return deliveryMapper.selectVoList(Wrappers.lambdaQuery(ErpPurchasePlanDelivery.class)
|
||||
.eq(ErpPurchasePlanDelivery::getPlanId, planId)
|
||||
.orderByAsc(ErpPurchasePlanDelivery::getTruckNo, ErpPurchasePlanDelivery::getDeliveryId));
|
||||
.orderByDesc(ErpPurchasePlanDelivery::getDeliveryId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ErpPurchasePlanDeliveryVo> queryDeliveryListByBatch(Long batchId) {
|
||||
return deliveryMapper.selectVoList(Wrappers.lambdaQuery(ErpPurchasePlanDelivery.class)
|
||||
.eq(ErpPurchasePlanDelivery::getBatchId, batchId)
|
||||
.orderByDesc(ErpPurchasePlanDelivery::getDeliveryId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ErpPurchasePlanDeliveryBatchVo> queryDeliveryBatches(Long planId) {
|
||||
return deliveryBatchMapper.selectVoList(Wrappers.lambdaQuery(ErpPurchasePlanDeliveryBatch.class)
|
||||
.eq(ErpPurchasePlanDeliveryBatch::getPlanId, planId)
|
||||
.orderByDesc(ErpPurchasePlanDeliveryBatch::getBatchId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<ErpPurchasePlanVo> queryDeliveryPlanPage(String keyword, PageQuery pageQuery) {
|
||||
ErpPurchasePlanBo bo = new ErpPurchasePlanBo();
|
||||
bo.setKeyword(keyword);
|
||||
LambdaQueryWrapper<ErpPurchasePlan> lqw = buildQueryWrapper(bo)
|
||||
.eq(ErpPurchasePlan::getAuditStatus, AUDIT_PASS);
|
||||
Page<ErpPurchasePlanVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
for (ErpPurchasePlanVo v : result.getRecords()) {
|
||||
fillProgress(v);
|
||||
List<Long> orderIds = relMapper.selectList(Wrappers.lambdaQuery(ErpPurchasePlanContractRel.class)
|
||||
.eq(ErpPurchasePlanContractRel::getPlanId, v.getPlanId())).stream()
|
||||
.map(ErpPurchasePlanContractRel::getOrderId).collect(Collectors.toList());
|
||||
if (!orderIds.isEmpty()) {
|
||||
v.setContractCodes(baseMapper.selectOrderCodes(orderIds));
|
||||
}
|
||||
}
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -413,124 +492,29 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
if (plan == null) {
|
||||
return;
|
||||
}
|
||||
// 1. 计划总到货 = Σ单卷重量
|
||||
BigDecimal arrived = deliveryMapper.sumCoilWeightByPlan(planId);
|
||||
if (arrived == null) {
|
||||
arrived = BigDecimal.ZERO;
|
||||
}
|
||||
plan.setArrivedWeight(arrived);
|
||||
|
||||
// 2. 明细级回填:到货行按 牌号+规格(厚×宽) 聚合,再顺序分配到匹配明细
|
||||
List<ErpPurchasePlanItem> items = itemMapper.selectList(Wrappers.lambdaQuery(ErpPurchasePlanItem.class)
|
||||
.eq(ErpPurchasePlanItem::getPlanId, planId));
|
||||
// 到货 = 累计所有上传批次中 WMS 已确认到货(arrived=1)的卷
|
||||
List<ErpPurchasePlanDelivery> deliveries = deliveryMapper.selectList(Wrappers.lambdaQuery(ErpPurchasePlanDelivery.class)
|
||||
.eq(ErpPurchasePlanDelivery::getPlanId, planId));
|
||||
|
||||
Map<String, BigDecimal> arrivedByKey = new HashMap<>();
|
||||
int arrivedCount = 0;
|
||||
BigDecimal arrivedWeight = BigDecimal.ZERO;
|
||||
for (ErpPurchasePlanDelivery d : deliveries) {
|
||||
String key = specKey(d.getGrade(), d.getSpec());
|
||||
if (key == null) {
|
||||
continue;
|
||||
}
|
||||
BigDecimal w = d.getCoilWeight() == null ? BigDecimal.ZERO : d.getCoilWeight();
|
||||
arrivedByKey.merge(key, w, BigDecimal::add);
|
||||
}
|
||||
|
||||
// 明细按 key 分组(保持原顺序),便于同规格多行顺序分配
|
||||
Map<String, List<ErpPurchasePlanItem>> itemsByKey = new LinkedHashMap<>();
|
||||
for (ErpPurchasePlanItem it : items) {
|
||||
it.setArrivedWeight(BigDecimal.ZERO);
|
||||
it.setItemStatus(ITEM_NOT_ARRIVED);
|
||||
String key = itemSpecKey(it.getGrade(), it.getThickness(), it.getWidth());
|
||||
if (key != null) {
|
||||
itemsByKey.computeIfAbsent(key, k -> new ArrayList<>()).add(it);
|
||||
if (d.getArrived() != null && d.getArrived() == 1) {
|
||||
arrivedCount++;
|
||||
arrivedWeight = arrivedWeight.add(d.getCoilWeight() == null ? BigDecimal.ZERO : d.getCoilWeight());
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, List<ErpPurchasePlanItem>> e : itemsByKey.entrySet()) {
|
||||
BigDecimal remaining = arrivedByKey.getOrDefault(e.getKey(), BigDecimal.ZERO);
|
||||
List<ErpPurchasePlanItem> group = e.getValue();
|
||||
for (int i = 0; i < group.size(); i++) {
|
||||
ErpPurchasePlanItem it = group.get(i);
|
||||
BigDecimal planned = it.getWeight() == null ? BigDecimal.ZERO : it.getWeight();
|
||||
// 末项吃掉剩余(含富余),其余按计划量封顶
|
||||
BigDecimal give = (i == group.size() - 1) ? remaining : remaining.min(planned);
|
||||
if (give.compareTo(BigDecimal.ZERO) < 0) {
|
||||
give = BigDecimal.ZERO;
|
||||
}
|
||||
it.setArrivedWeight(give);
|
||||
remaining = remaining.subtract(give);
|
||||
if (remaining.compareTo(BigDecimal.ZERO) < 0) {
|
||||
remaining = BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 明细状态 + 是否全部到货
|
||||
boolean allArrived = !items.isEmpty();
|
||||
for (ErpPurchasePlanItem it : items) {
|
||||
BigDecimal planned = it.getWeight() == null ? BigDecimal.ZERO : it.getWeight();
|
||||
BigDecimal aw = it.getArrivedWeight() == null ? BigDecimal.ZERO : it.getArrivedWeight();
|
||||
if (aw.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
it.setItemStatus(ITEM_NOT_ARRIVED);
|
||||
} else if (planned.compareTo(BigDecimal.ZERO) > 0 && aw.compareTo(planned) >= 0) {
|
||||
it.setItemStatus(ITEM_ARRIVED);
|
||||
} else {
|
||||
it.setItemStatus(ITEM_PARTIAL);
|
||||
}
|
||||
if (!ITEM_ARRIVED.equals(it.getItemStatus())) {
|
||||
allArrived = false;
|
||||
}
|
||||
itemMapper.updateById(it);
|
||||
}
|
||||
|
||||
// 4. 计划状态:所有明细到货 或 总量达标 → 自动归档
|
||||
plan.setArrivedCount(arrivedCount);
|
||||
plan.setArrivedWeight(arrivedWeight);
|
||||
// 到货重量达到计划要求总重量 → 自动归档
|
||||
BigDecimal planWeight = plan.getPlanWeight() == null ? BigDecimal.ZERO : plan.getPlanWeight();
|
||||
boolean weightDone = planWeight.compareTo(BigDecimal.ZERO) > 0 && arrived.compareTo(planWeight) >= 0;
|
||||
plan.setPlanStatus((allArrived || weightDone) ? PLAN_STATUS_ARCHIVED : PLAN_STATUS_ONGOING);
|
||||
boolean done = planWeight.compareTo(BigDecimal.ZERO) > 0 && arrivedWeight.compareTo(planWeight) >= 0;
|
||||
plan.setPlanStatus(done ? PLAN_STATUS_ARCHIVED : PLAN_STATUS_ONGOING);
|
||||
baseMapper.updateById(plan);
|
||||
}
|
||||
|
||||
/** 到货行规格 key:牌号 + 厚×宽(规格形如 "3.00×1230") */
|
||||
private String specKey(String grade, String spec) {
|
||||
if (StringUtils.isBlank(spec)) {
|
||||
return null;
|
||||
}
|
||||
String[] parts = spec.split("[×xX*]");
|
||||
if (parts.length < 2) {
|
||||
return null;
|
||||
}
|
||||
String t = normNum(parts[0]);
|
||||
String w = normNum(parts[1]);
|
||||
if (t == null || w == null) {
|
||||
return null;
|
||||
}
|
||||
return normGrade(grade) + "|" + t + "×" + w;
|
||||
}
|
||||
|
||||
/** 明细规格 key:牌号 + 厚×宽 */
|
||||
private String itemSpecKey(String grade, String thickness, String width) {
|
||||
String t = normNum(thickness);
|
||||
String w = normNum(width);
|
||||
if (t == null || w == null) {
|
||||
return null;
|
||||
}
|
||||
return normGrade(grade) + "|" + t + "×" + w;
|
||||
}
|
||||
|
||||
private String normGrade(String g) {
|
||||
return g == null ? "" : g.trim().toUpperCase();
|
||||
}
|
||||
|
||||
/** 数字归一:去尾零,无法解析(区间文本等)返回 null 不参与匹配 */
|
||||
private String normNum(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new BigDecimal(s.trim()).stripTrailingZeros().toPlainString();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
/** 卷号归一:去空白转大写,便于匹配(DB 校对集已大小写不敏感,这里再兜底) */
|
||||
private String normCoil(String s) {
|
||||
return s == null ? "" : s.trim().toUpperCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -545,13 +529,21 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
|
||||
return map;
|
||||
}
|
||||
|
||||
/** 进度百分比(0-100,保留2位) */
|
||||
private BigDecimal calcProgress(BigDecimal arrived, BigDecimal planWeight) {
|
||||
if (planWeight == null || planWeight.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
/** 同时填充按重量、按卷数两个到货百分比 */
|
||||
private void fillProgress(ErpPurchasePlanVo v) {
|
||||
v.setProgress(calcPct(v.getArrivedWeight(), v.getPlanWeight()));
|
||||
v.setProgressQty(calcPct(
|
||||
v.getArrivedCount() == null ? null : BigDecimal.valueOf(v.getArrivedCount()),
|
||||
v.getPlanQty() == null ? null : BigDecimal.valueOf(v.getPlanQty())));
|
||||
}
|
||||
|
||||
/** 百分比(0-100,保留2位) = num/den,封顶100 */
|
||||
private BigDecimal calcPct(BigDecimal num, BigDecimal den) {
|
||||
if (den == null || den.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
BigDecimal a = arrived == null ? BigDecimal.ZERO : arrived;
|
||||
BigDecimal pct = a.multiply(BigDecimal.valueOf(100)).divide(planWeight, 2, RoundingMode.HALF_UP);
|
||||
BigDecimal n = num == null ? BigDecimal.ZERO : num;
|
||||
BigDecimal pct = n.multiply(BigDecimal.valueOf(100)).divide(den, 2, RoundingMode.HALF_UP);
|
||||
return pct.min(BigDecimal.valueOf(100));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.klp.erp.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.klp.erp.domain.ErpSupplierTransaction;
|
||||
import com.klp.erp.domain.bo.ErpSupplierTransactionBo;
|
||||
import com.klp.erp.domain.vo.ErpSupplierTransactionVo;
|
||||
import com.klp.erp.mapper.ErpSupplierTransactionMapper;
|
||||
import com.klp.erp.service.IErpSupplierTransactionService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 供应商往来流水Service业务层处理
|
||||
*
|
||||
* @author klp
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class ErpSupplierTransactionServiceImpl implements IErpSupplierTransactionService {
|
||||
|
||||
/** 类型常量 */
|
||||
private static final String TYPE_PAYABLE = "1"; // 采购应付
|
||||
private static final String TYPE_PAY = "2"; // 付款
|
||||
private static final String TYPE_RETURN = "3"; // 退货
|
||||
private static final String TYPE_OTHER = "4"; // 其他
|
||||
|
||||
private final ErpSupplierTransactionMapper baseMapper;
|
||||
|
||||
@Override
|
||||
public List<ErpSupplierTransactionVo> queryBySupplier(Long supplierId) {
|
||||
if (supplierId == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
// 按发生时间升序计算逐笔累计余额,再倒序返回(最新在前)
|
||||
List<ErpSupplierTransactionVo> asc = baseMapper.selectVoList(Wrappers.lambdaQuery(ErpSupplierTransaction.class)
|
||||
.eq(ErpSupplierTransaction::getSupplierId, supplierId)
|
||||
.orderByAsc(ErpSupplierTransaction::getTxnDate)
|
||||
.orderByAsc(ErpSupplierTransaction::getTxnId));
|
||||
BigDecimal running = BigDecimal.ZERO;
|
||||
for (ErpSupplierTransactionVo v : asc) {
|
||||
running = running.add(signedAmount(v.getTxnType(), v.getAmount()));
|
||||
v.setBalance(running);
|
||||
}
|
||||
Collections.reverse(asc);
|
||||
return asc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> summary(Long supplierId) {
|
||||
Map<String, Object> map = new HashMap<>(8);
|
||||
BigDecimal payable = BigDecimal.ZERO;
|
||||
BigDecimal paid = BigDecimal.ZERO;
|
||||
BigDecimal returned = BigDecimal.ZERO;
|
||||
BigDecimal other = BigDecimal.ZERO;
|
||||
BigDecimal balance = BigDecimal.ZERO;
|
||||
if (supplierId != null) {
|
||||
List<ErpSupplierTransactionVo> list = baseMapper.selectVoList(Wrappers.lambdaQuery(ErpSupplierTransaction.class)
|
||||
.eq(ErpSupplierTransaction::getSupplierId, supplierId));
|
||||
for (ErpSupplierTransactionVo v : list) {
|
||||
BigDecimal amt = v.getAmount() == null ? BigDecimal.ZERO : v.getAmount();
|
||||
if (TYPE_PAYABLE.equals(v.getTxnType())) {
|
||||
payable = payable.add(amt);
|
||||
} else if (TYPE_PAY.equals(v.getTxnType())) {
|
||||
paid = paid.add(amt);
|
||||
} else if (TYPE_RETURN.equals(v.getTxnType())) {
|
||||
returned = returned.add(amt);
|
||||
} else {
|
||||
other = other.add(amt);
|
||||
}
|
||||
balance = balance.add(signedAmount(v.getTxnType(), amt));
|
||||
}
|
||||
}
|
||||
map.put("payable", payable);
|
||||
map.put("paid", paid);
|
||||
map.put("returned", returned);
|
||||
map.put("other", other);
|
||||
map.put("balance", balance);
|
||||
return map;
|
||||
}
|
||||
|
||||
/** 付款/退货为负方向,其余为正方向 */
|
||||
private BigDecimal signedAmount(String type, BigDecimal amount) {
|
||||
BigDecimal amt = amount == null ? BigDecimal.ZERO : amount;
|
||||
if (TYPE_PAY.equals(type) || TYPE_RETURN.equals(type)) {
|
||||
return amt.negate();
|
||||
}
|
||||
return amt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean insertByBo(ErpSupplierTransactionBo bo) {
|
||||
ErpSupplierTransaction add = BeanUtil.toBean(bo, ErpSupplierTransaction.class);
|
||||
return baseMapper.insert(add) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean updateByBo(ErpSupplierTransactionBo bo) {
|
||||
ErpSupplierTransaction update = BeanUtil.toBean(bo, ErpSupplierTransaction.class);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean deleteByIds(Collection<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
ORDER BY p.plan_id DESC
|
||||
</select>
|
||||
|
||||
<!-- WMS 钢卷表中已存在(非占位 data_type<>10)的厂家卷号,用于到货卷号校验 -->
|
||||
<select id="selectExistingSupplierCoilNos" resultType="java.lang.String">
|
||||
SELECT DISTINCT supplier_coil_no
|
||||
FROM wms_material_coil
|
||||
WHERE del_flag = 0 AND data_type <> 10
|
||||
AND supplier_coil_no IN
|
||||
<foreach collection="coilNos" item="c" open="(" separator="," close=")">
|
||||
#{c}
|
||||
</foreach>
|
||||
</select>
|
||||
|
||||
<!-- 按合同关键字查关联的采购计划ID(订单编号/合同号/合同名称) -->
|
||||
<select id="selectPlanIdsByContractKeyword" resultType="java.lang.Long">
|
||||
SELECT DISTINCT r.plan_id
|
||||
|
||||
@@ -32,6 +32,46 @@ export function delSupplier(ids) {
|
||||
})
|
||||
}
|
||||
|
||||
// 供应商往来流水
|
||||
export function listSupplierTxn(supplierId) {
|
||||
return request({
|
||||
url: '/erp/supplierTxn/list',
|
||||
method: 'get',
|
||||
params: { supplierId }
|
||||
})
|
||||
}
|
||||
|
||||
export function supplierTxnSummary(supplierId) {
|
||||
return request({
|
||||
url: '/erp/supplierTxn/summary',
|
||||
method: 'get',
|
||||
params: { supplierId }
|
||||
})
|
||||
}
|
||||
|
||||
export function addSupplierTxn(data) {
|
||||
return request({
|
||||
url: '/erp/supplierTxn',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateSupplierTxn(data) {
|
||||
return request({
|
||||
url: '/erp/supplierTxn',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function delSupplierTxn(ids) {
|
||||
return request({
|
||||
url: `/erp/supplierTxn/${ids}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 供应商价格
|
||||
export function listSupplierPrice(query) {
|
||||
return request({
|
||||
|
||||
@@ -115,6 +115,31 @@ export function listDelivery(planId) {
|
||||
})
|
||||
}
|
||||
|
||||
// 到货记录页左侧:审核通过的计划
|
||||
export function listDeliveryPlans(query) {
|
||||
return request({
|
||||
url: '/erp/purchasePlan/deliveryPlans',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 某计划的到货上传批次(每次上传一条)
|
||||
export function listDeliveryBatches(planId) {
|
||||
return request({
|
||||
url: `/erp/purchasePlan/${planId}/deliveryBatches`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 某批次的到货明细
|
||||
export function listDeliveryByBatch(batchId) {
|
||||
return request({
|
||||
url: `/erp/purchasePlan/deliveryBatch/${batchId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 删除到货明细
|
||||
export function delDelivery(deliveryId) {
|
||||
return request({
|
||||
|
||||
332
klp-ui/src/views/erp/purchaseDelivery/index.vue
Normal file
332
klp-ui/src/views/erp/purchaseDelivery/index.vue
Normal file
@@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<div class="pd-wb">
|
||||
<div class="pd-main">
|
||||
<!-- 左:审核通过的计划 -->
|
||||
<aside class="pd-col pd-plans">
|
||||
<div class="pd-col-tool">
|
||||
<el-input
|
||||
v-model="queryParams.keyword"
|
||||
size="small"
|
||||
clearable
|
||||
placeholder="搜索计划号 / 合同号 / 供货商"
|
||||
prefix-icon="el-icon-search"
|
||||
@keyup.enter.native="handleQuery"
|
||||
@clear="handleQuery"
|
||||
/>
|
||||
</div>
|
||||
<ul class="pd-list" v-loading="loading">
|
||||
<li
|
||||
v-for="p in planList"
|
||||
:key="p.planId"
|
||||
class="pd-li"
|
||||
:class="{ active: current.planId === p.planId }"
|
||||
@click="selectPlan(p)"
|
||||
>
|
||||
<div class="pd-li-r1">
|
||||
<span class="pd-no">{{ p.planNo }}</span>
|
||||
<span class="pd-pct" :class="{ done: Number(p.progress) >= 100 }">{{ (Number(p.progress) || 0).toFixed(0) }}%</span>
|
||||
</div>
|
||||
<div class="pd-li-r2">{{ (p.contractCodes || []).join('、') || '无合同号' }}</div>
|
||||
<el-progress :percentage="Number(p.progress) || 0" :stroke-width="4" :show-text="false" :color="progressColor" />
|
||||
<div class="pd-li-r3">
|
||||
<span>{{ p.supplier || '—' }}</span>
|
||||
<span>{{ p.arrivedCount || 0 }}/{{ p.planQty || 0 }} 卷<i v-if="p.planStatus === '1'" class="pd-arch">已齐</i></span>
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="!loading && !planList.length" class="pd-empty">暂无审核通过的计划</li>
|
||||
</ul>
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
:pager-count="5"
|
||||
layout="prev, pager, next"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</aside>
|
||||
|
||||
<!-- 右:到货跟踪 -->
|
||||
<section class="pd-col pd-detail">
|
||||
<div v-if="!current.planId" class="pd-placeholder">
|
||||
<i class="el-icon-truck"></i>
|
||||
<p>从左侧选择一条审核通过的计划,上传到货表格按卷号比对</p>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div class="pd-d-head">
|
||||
<div>
|
||||
<span class="pd-d-title">{{ current.planNo }}</span>
|
||||
<span class="pd-badge" :class="current.planStatus === '1' ? 'p1' : 'p0'">{{ current.planStatus === '1' ? '已到齐' : '到货中' }}</span>
|
||||
</div>
|
||||
<el-upload
|
||||
:headers="upload.headers"
|
||||
:action="uploadUrl"
|
||||
:show-file-list="false"
|
||||
accept=".xlsx,.xls"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
>
|
||||
<el-button type="primary" size="small" icon="el-icon-upload2">上传到货表格</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
|
||||
<div class="pd-meta">
|
||||
<div class="pd-meta-i"><label>合同号</label><span>{{ (current.contractCodes || []).join('、') || '—' }}</span></div>
|
||||
<div class="pd-meta-i"><label>供货商</label><span>{{ current.supplier || '—' }}</span></div>
|
||||
<div class="pd-meta-i"><label>已到货 / 要求</label><span>{{ current.arrivedCount || 0 }} / {{ current.planQty || 0 }} 卷 · {{ fmt(current.arrivedWeight) }} / {{ fmt(current.planWeight) }} T</span></div>
|
||||
<div class="pd-meta-i wide">
|
||||
<label>到货百分比(按重量)</label>
|
||||
<el-progress
|
||||
:percentage="Number(current.progress) || 0"
|
||||
:stroke-width="14" :text-inside="true" :color="progressColor"
|
||||
:format="p => p.toFixed(1) + '%'"
|
||||
/>
|
||||
</div>
|
||||
<div class="pd-meta-i wide">
|
||||
<label>到货百分比(按卷数)</label>
|
||||
<el-progress
|
||||
:percentage="Number(current.progressQty) || 0"
|
||||
:stroke-width="14" :text-inside="true" color="#3a8a4d"
|
||||
:format="p => p.toFixed(1) + '%'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab" class="pd-tabs">
|
||||
<el-tab-pane name="items">
|
||||
<span slot="label">采购要求(到货 重量{{ (Number(current.progress) || 0).toFixed(0) }}% / 卷数{{ (Number(current.progressQty) || 0).toFixed(0) }}%)</span>
|
||||
<p class="pd-req-tip">采购要求只说明买什么、买多少;到货按上传表格的卷号在钢卷表确认,进度见上方与「到货记录」。</p>
|
||||
<el-table :data="current.items" border size="mini" max-height="420">
|
||||
<el-table-column label="#" type="index" width="40" align="center" />
|
||||
<el-table-column label="产品" prop="productType" min-width="70" show-overflow-tooltip />
|
||||
<el-table-column label="材质" prop="material" min-width="78" show-overflow-tooltip />
|
||||
<el-table-column label="牌号" prop="grade" min-width="72" />
|
||||
<el-table-column label="宽度" prop="width" min-width="68" />
|
||||
<el-table-column label="厚度" prop="thickness" min-width="68" />
|
||||
<el-table-column label="宽公差" prop="widthTolerance" min-width="70" />
|
||||
<el-table-column label="厚公差" prop="thicknessTolerance" min-width="70" />
|
||||
<el-table-column label="重量(T)" prop="weight" min-width="76" align="right" />
|
||||
<el-table-column label="数量" prop="quantity" min-width="58" align="right" />
|
||||
<el-table-column label="供货商" prop="supplier" min-width="88" show-overflow-tooltip />
|
||||
<template slot="empty"><span>无采购明细</span></template>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane name="batches">
|
||||
<span slot="label">到货记录({{ batches.length }} 次上传)</span>
|
||||
<div class="pd-batch-wrap">
|
||||
<el-table
|
||||
:data="batches"
|
||||
border size="mini" max-height="200" v-loading="batchLoading"
|
||||
highlight-current-row @row-click="viewBatch"
|
||||
>
|
||||
<el-table-column label="上传时间" prop="createTime" width="150" align="center" />
|
||||
<el-table-column label="文件" prop="fileName" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="行数" prop="rowCount" width="64" align="center" />
|
||||
<el-table-column label="匹配卷数" prop="matchedCount" width="84" align="center" />
|
||||
<el-table-column label="上传后到货%" width="110" align="center">
|
||||
<template slot-scope="s">{{ (Number(s.row.arrivedPercent) || 0).toFixed(0) }}%</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="录入人" prop="createBy" width="90" align="center" />
|
||||
<el-table-column label="操作" width="64" align="center">
|
||||
<template slot-scope="s"><el-button type="text" size="mini" @click.stop="viewBatch(s.row)">查看</el-button></template>
|
||||
</el-table-column>
|
||||
<template slot="empty"><span>暂无上传记录,点右上角「上传到货表格」</span></template>
|
||||
</el-table>
|
||||
|
||||
<div class="pd-batch-rows" v-if="currentBatch.batchId">
|
||||
<div class="pd-sub-title">「{{ currentBatch.fileName }}」到货明细(卷号在钢卷表确认到货的高亮为已到货)</div>
|
||||
<el-table :data="batchRows" border stripe size="mini" max-height="300" v-loading="batchRowsLoading"
|
||||
:row-class-name="rowClass">
|
||||
<el-table-column label="日期" prop="arrivalDate" width="100" align="center" />
|
||||
<el-table-column label="牌号" prop="grade" width="78" align="center" />
|
||||
<el-table-column label="规格" prop="spec" width="105" align="center" />
|
||||
<el-table-column label="卷号" prop="coilNo" width="120" align="center" />
|
||||
<el-table-column label="单卷(T)" prop="coilWeight" width="78" align="right" />
|
||||
<el-table-column label="车号" prop="truckNo" width="95" align="center" />
|
||||
<el-table-column label="件数" prop="pieceCount" width="56" align="center" />
|
||||
<el-table-column label="销售" prop="salesCode" width="80" align="center" />
|
||||
<el-table-column label="到货" width="72" align="center">
|
||||
<template slot-scope="s">
|
||||
<span class="pd-mtag" :class="s.row.arrived === 1 ? 'yes' : 'no'">{{ s.row.arrived === 1 ? '已到货' : '未到' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template slot="empty"><span>该批次无明细</span></template>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getPurchasePlan,
|
||||
listDeliveryPlans,
|
||||
listDeliveryBatches,
|
||||
listDeliveryByBatch
|
||||
} from '@/api/erp/purchasePlan'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
name: 'ErpPurchaseDelivery',
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
total: 0,
|
||||
planList: [],
|
||||
queryParams: { pageNum: 1, pageSize: 20, keyword: undefined },
|
||||
current: {},
|
||||
activeTab: 'items',
|
||||
batches: [],
|
||||
batchLoading: false,
|
||||
currentBatch: {},
|
||||
batchRows: [],
|
||||
batchRowsLoading: false,
|
||||
upload: { headers: { Authorization: 'Bearer ' + getToken() } },
|
||||
progressColor: '#5b8db8'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
uploadUrl() {
|
||||
return process.env.VUE_APP_BASE_API + '/erp/purchasePlan/' + (this.current.planId || '') + '/importDelivery'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
getList(keepCurrent) {
|
||||
this.loading = true
|
||||
listDeliveryPlans(this.queryParams).then(res => {
|
||||
this.planList = res.rows || []
|
||||
this.total = res.total || 0
|
||||
this.loading = false
|
||||
if (!keepCurrent) {
|
||||
if (this.planList.length) this.selectPlan(this.planList[0])
|
||||
else this.current = {}
|
||||
}
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
selectPlan(p) {
|
||||
this.activeTab = 'items'
|
||||
this.current = { ...p }
|
||||
this.currentBatch = {}
|
||||
this.batchRows = []
|
||||
this.refreshDetail()
|
||||
},
|
||||
refreshDetail() {
|
||||
const planId = this.current.planId
|
||||
if (!planId) return
|
||||
getPurchasePlan(planId).then(res => { this.current = { ...this.current, ...(res.data || {}) } })
|
||||
this.loadBatches()
|
||||
},
|
||||
loadBatches() {
|
||||
this.batchLoading = true
|
||||
listDeliveryBatches(this.current.planId).then(res => {
|
||||
this.batches = res.data || []
|
||||
}).finally(() => { this.batchLoading = false })
|
||||
},
|
||||
viewBatch(b) {
|
||||
this.currentBatch = { ...b }
|
||||
this.batchRowsLoading = true
|
||||
listDeliveryByBatch(b.batchId).then(res => { this.batchRows = res.data || [] }).finally(() => { this.batchRowsLoading = false })
|
||||
},
|
||||
rowClass({ row }) {
|
||||
return row.arrived === 1 ? 'pd-row-matched' : ''
|
||||
},
|
||||
handleUploadSuccess(res) {
|
||||
if (res.code === 200) {
|
||||
const data = res.data || {}
|
||||
if (data.kgConverted) {
|
||||
this.$alert(res.msg, '导入完成(含单位纠正)', { dangerouslyUseHTMLString: true, type: 'warning' })
|
||||
} else {
|
||||
this.$modal.msgSuccess(res.msg || '导入成功')
|
||||
}
|
||||
this.activeTab = 'batches'
|
||||
this.refreshDetail()
|
||||
this.getList(true)
|
||||
} else {
|
||||
this.$alert(res.msg || '导入失败', '到货文件校验未通过', { dangerouslyUseHTMLString: true, type: 'error' })
|
||||
}
|
||||
},
|
||||
handleUploadError() {
|
||||
this.$modal.msgError('上传失败,请检查文件后重试')
|
||||
},
|
||||
fmt(v) {
|
||||
return Number(v || 0).toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 3 })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$accent: #5b8db8;
|
||||
$line: #e4e7ed;
|
||||
$ink: #303133;
|
||||
$sub: #909399;
|
||||
|
||||
.pd-wb { height: calc(100vh - 84px); display: flex; flex-direction: column; background: #f5f6f8; padding: 12px; box-sizing: border-box; }
|
||||
.pd-main { flex: 1; display: flex; gap: 12px; min-height: 0; }
|
||||
.pd-col { background: #fff; border: 1px solid $line; display: flex; flex-direction: column; min-height: 0; }
|
||||
.pd-plans { width: 300px; flex-shrink: 0; }
|
||||
.pd-detail { flex: 1; min-width: 0; overflow-y: auto; }
|
||||
|
||||
.pd-col-tool { padding: 10px; border-bottom: 1px solid $line; ::v-deep .el-input { width: 100%; } }
|
||||
.pd-list { flex: 1; overflow-y: auto; margin: 0; padding: 0; list-style: none; }
|
||||
.pd-li {
|
||||
padding: 10px 12px; border-bottom: 1px solid #f0f2f5; cursor: pointer; border-left: 3px solid transparent;
|
||||
&:hover { background: #f7f9fb; }
|
||||
&.active { background: #eef3f8; border-left-color: $accent; }
|
||||
}
|
||||
.pd-li-r1 { display: flex; justify-content: space-between; align-items: center; }
|
||||
.pd-no { font-size: 13px; font-weight: 600; color: $ink; }
|
||||
.pd-pct { font-size: 12px; font-weight: 600; color: $sub; &.done { color: #3a8a4d; } }
|
||||
.pd-li-r2 { font-size: 12px; color: $sub; margin: 4px 0 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.pd-li-r3 { display: flex; justify-content: space-between; font-size: 11px; color: $sub; margin-top: 4px; }
|
||||
.pd-arch { font-style: normal; color: #3a8a4d; margin-left: 6px; }
|
||||
.pd-empty { text-align: center; color: $sub; padding: 36px 12px; font-size: 13px; }
|
||||
|
||||
.pd-placeholder {
|
||||
height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; color: $sub;
|
||||
i { font-size: 46px; margin-bottom: 12px; color: #d6dce1; }
|
||||
p { font-size: 13px; }
|
||||
}
|
||||
.pd-d-head { display: flex; justify-content: space-between; align-items: center; padding: 14px 18px; border-bottom: 1px solid $line; }
|
||||
.pd-d-title { font-size: 15px; font-weight: 600; color: $ink; margin-right: 8px; }
|
||||
.pd-badge {
|
||||
font-size: 11px; line-height: 16px; padding: 0 6px; border-radius: 2px; border: 1px solid #dcdfe6; color: $sub; background: #fafafa;
|
||||
&.p1 { color: #3a8a4d; border-color: #b7d9bf; background: #f0f9f1; }
|
||||
}
|
||||
.pd-meta { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px 24px; padding: 16px 18px; }
|
||||
.pd-meta-i {
|
||||
display: flex; flex-direction: column; font-size: 13px;
|
||||
label { color: $sub; font-size: 12px; margin-bottom: 3px; }
|
||||
span { color: $ink; }
|
||||
&.wide { grid-column: span 3; }
|
||||
}
|
||||
.pd-tabs { padding: 0 18px 18px; }
|
||||
.pd-nocoil { color: #c0c4cc; }
|
||||
.pd-req-tip { font-size: 12px; color: #909399; margin: 0 0 10px; line-height: 1.6; }
|
||||
.pd-istat {
|
||||
font-size: 11px; line-height: 16px; padding: 0 6px; border-radius: 2px; border: 1px solid #dcdfe6; color: $sub;
|
||||
&.s2 { color: #3a8a4d; border-color: #b7d9bf; background: #f0f9f1; }
|
||||
}
|
||||
.pd-batch-rows { margin-top: 14px; }
|
||||
.pd-sub-title { font-size: 13px; font-weight: 600; color: $ink; border-left: 3px solid $accent; padding-left: 8px; margin: 6px 0 10px; }
|
||||
.pd-mtag {
|
||||
font-size: 11px; line-height: 16px; padding: 0 6px; border-radius: 2px; border: 1px solid #dcdfe6; color: $sub;
|
||||
&.yes { color: #3a8a4d; border-color: #b7d9bf; background: #f0f9f1; }
|
||||
&.no { color: $sub; }
|
||||
}
|
||||
::v-deep .pd-row-matched { background: #f3f9f4; }
|
||||
</style>
|
||||
@@ -126,9 +126,6 @@
|
||||
<el-table-column label="牌号" min-width="75">
|
||||
<template slot-scope="s"><el-input v-model="s.row.grade" size="mini" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="卷号" min-width="95">
|
||||
<template slot-scope="s"><el-input v-model="s.row.coilNo" size="mini" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="宽度" min-width="75">
|
||||
<template slot-scope="s"><el-input v-model="s.row.width" size="mini" /></template>
|
||||
</el-table-column>
|
||||
@@ -224,54 +221,16 @@
|
||||
<el-table-column label="产品" prop="productType" min-width="70" show-overflow-tooltip />
|
||||
<el-table-column label="材质" prop="material" min-width="80" show-overflow-tooltip />
|
||||
<el-table-column label="牌号" prop="grade" min-width="75" />
|
||||
<el-table-column label="卷号" prop="coilNo" min-width="95" show-overflow-tooltip />
|
||||
<el-table-column label="宽度" prop="width" min-width="70" />
|
||||
<el-table-column label="厚度" prop="thickness" min-width="70" />
|
||||
<el-table-column label="宽公差" prop="widthTolerance" min-width="72" />
|
||||
<el-table-column label="厚公差" prop="thicknessTolerance" min-width="72" />
|
||||
<el-table-column label="重量(T)" prop="weight" min-width="78" align="right" />
|
||||
<el-table-column label="数量" prop="quantity" min-width="60" align="right" />
|
||||
<el-table-column label="已到货(T)" prop="arrivedWeight" min-width="82" align="right" />
|
||||
<el-table-column label="到货状态" min-width="84" align="center">
|
||||
<template slot-scope="s">
|
||||
<span class="pp-istat" :class="'s' + (s.row.itemStatus || '0')">{{ itemStatusText(s.row.itemStatus) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="供货商" prop="supplier" min-width="90" show-overflow-tooltip />
|
||||
<template slot="empty"><span>无明细</span></template>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="到货进度" name="delivery">
|
||||
<div class="pp-deliv-bar">
|
||||
<span class="pp-deliv-tip" v-if="current.auditStatus !== '1'">审核通过后才能上传到货 Excel</span>
|
||||
<el-upload
|
||||
v-else
|
||||
:headers="upload.headers"
|
||||
:action="uploadUrl"
|
||||
:show-file-list="false"
|
||||
accept=".xlsx,.xls"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
>
|
||||
<el-button type="primary" size="small" icon="el-icon-upload2">上传到货 Excel</el-button>
|
||||
</el-upload>
|
||||
<span class="pp-deliv-hint">上传后按牌号+规格回填明细到货量与状态</span>
|
||||
</div>
|
||||
<el-table :data="deliveryList" border stripe size="mini" max-height="340" v-loading="deliveryLoading">
|
||||
<el-table-column label="日期" prop="arrivalDate" width="100" align="center" />
|
||||
<el-table-column label="牌号" prop="grade" width="80" align="center" />
|
||||
<el-table-column label="规格" prop="spec" width="110" align="center" />
|
||||
<el-table-column label="卷号" prop="coilNo" width="110" align="center" />
|
||||
<el-table-column label="单卷(T)" prop="coilWeight" width="80" align="right" />
|
||||
<el-table-column label="车号" prop="truckNo" width="100" align="center" />
|
||||
<el-table-column label="整车(T)" prop="truckWeight" width="80" align="right" />
|
||||
<el-table-column label="件数" prop="pieceCount" width="56" align="center" />
|
||||
<el-table-column label="销售" prop="salesCode" width="85" align="center" />
|
||||
<el-table-column label="到站" prop="arrivalStation" width="90" align="center" />
|
||||
<el-table-column label="" width="36" align="center">
|
||||
<template slot-scope="s"><i class="el-icon-delete pp-del" @click="removeDelivery(s.row)" /></template>
|
||||
</el-table-column>
|
||||
<template slot="empty"><span>暂无到货记录</span></template>
|
||||
</el-table>
|
||||
<p class="pp-deliv-hint" v-if="current.auditStatus === '1'">到货上传与卷号比对请到「到货记录」页面</p>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
@@ -370,7 +329,6 @@
|
||||
<el-col :span="12"><el-form-item label="产品"><el-input v-model="batchFill.productType" placeholder="如 热轧卷板" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="材质"><el-input v-model="batchFill.material" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="牌号"><el-input v-model="batchFill.grade" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="卷号"><el-input v-model="batchFill.coilNo" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="宽度"><el-input v-model="batchFill.width" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="厚度"><el-input v-model="batchFill.thickness" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="宽公差"><el-input v-model="batchFill.widthTolerance" /></el-form-item></el-col>
|
||||
@@ -408,12 +366,9 @@ import {
|
||||
updatePurchasePlan,
|
||||
delPurchasePlan,
|
||||
submitPurchasePlan,
|
||||
listDelivery,
|
||||
delDelivery,
|
||||
listContracts
|
||||
} from '@/api/erp/purchasePlan'
|
||||
import { listSupplier } from '@/api/erp/purchase'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
name: 'ErpPurchasePlan',
|
||||
@@ -436,7 +391,7 @@ export default {
|
||||
batchFillOpen: false,
|
||||
batchFillNewRow: true,
|
||||
batchRows: 0,
|
||||
batchFill: { productType: '热轧卷板', material: '', grade: '', coilNo: '', width: '', thickness: '', widthTolerance: '', thicknessTolerance: '', weight: '', quantity: '', supplier: '' },
|
||||
batchFill: { productType: '热轧卷板', material: '', grade: '', width: '', thickness: '', widthTolerance: '', thicknessTolerance: '', weight: '', quantity: '', supplier: '' },
|
||||
newRowDefaults: null,
|
||||
// 合同选择器
|
||||
pickerOpen: false,
|
||||
@@ -451,17 +406,10 @@ export default {
|
||||
supplierList: [],
|
||||
supplierTotal: 0,
|
||||
supplierQuery: { pageNum: 1, pageSize: 10, name: undefined },
|
||||
// 到货
|
||||
deliveryList: [],
|
||||
deliveryLoading: false,
|
||||
upload: { headers: { Authorization: 'Bearer ' + getToken() } },
|
||||
progressColor: '#5b8db8'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
uploadUrl() {
|
||||
return process.env.VUE_APP_BASE_API + '/erp/purchasePlan/' + (this.current.planId || '') + '/importDelivery'
|
||||
},
|
||||
itemsWeight() {
|
||||
return (this.form.items || []).reduce((s, i) => s + (Number(i.weight) || 0), 0).toFixed(3)
|
||||
}
|
||||
@@ -497,15 +445,13 @@ export default {
|
||||
const planId = this.current.planId
|
||||
if (!planId) return
|
||||
getPurchasePlan(planId).then(res => { this.current = { ...this.current, ...(res.data || {}) } })
|
||||
this.deliveryLoading = true
|
||||
listDelivery(planId).then(res => { this.deliveryList = res.data || [] }).finally(() => { this.deliveryLoading = false })
|
||||
},
|
||||
// ---- 新增 / 编辑 ----
|
||||
resetForm() {
|
||||
this.form = { planId: null, planNo: '', supplier: '', purchaseDate: '', remark: '', items: [], orderIds: [], contractCodes: [] }
|
||||
this.selectedContracts = []
|
||||
this.newRowDefaults = null
|
||||
this.batchFill = { productType: '热轧卷板', material: '', grade: '', coilNo: '', width: '', thickness: '', widthTolerance: '', thicknessTolerance: '', weight: '', quantity: '', supplier: '' }
|
||||
this.batchFill = { productType: '热轧卷板', material: '', grade: '', width: '', thickness: '', widthTolerance: '', thicknessTolerance: '', weight: '', quantity: '', supplier: '' }
|
||||
this.batchRows = 0
|
||||
},
|
||||
handleAdd() {
|
||||
@@ -605,7 +551,6 @@ export default {
|
||||
productType: it.productType || '热轧卷板',
|
||||
material: it.material || '',
|
||||
grade: it.grade || '',
|
||||
coilNo: '',
|
||||
width: it.width || '',
|
||||
thickness: it.thickness || '',
|
||||
widthTolerance: it.widthTolerance || '0',
|
||||
@@ -618,7 +563,7 @@ export default {
|
||||
})
|
||||
},
|
||||
blankItem() {
|
||||
return { productType: '热轧卷板', material: '', grade: '', coilNo: '', width: '', thickness: '', widthTolerance: '0', thicknessTolerance: '0', weight: '', quantity: '', supplier: '' }
|
||||
return { productType: '热轧卷板', material: '', grade: '', width: '', thickness: '', widthTolerance: '0', thicknessTolerance: '0', weight: '', quantity: '', supplier: '' }
|
||||
},
|
||||
addItem() {
|
||||
// 优先用批量默认值,其次继承上一行,最后空行;重量每行不同故清空
|
||||
@@ -641,7 +586,7 @@ export default {
|
||||
},
|
||||
batchFillKeys() {
|
||||
const f = this.batchFill
|
||||
const keys = ['productType', 'material', 'grade', 'coilNo', 'width', 'thickness', 'widthTolerance', 'thicknessTolerance', 'weight', 'quantity', 'supplier']
|
||||
const keys = ['productType', 'material', 'grade', 'width', 'thickness', 'widthTolerance', 'thicknessTolerance', 'weight', 'quantity', 'supplier']
|
||||
return keys.filter(k => f[k] !== '' && f[k] != null)
|
||||
},
|
||||
applyBatchFill() {
|
||||
@@ -719,38 +664,11 @@ export default {
|
||||
this.getList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
// ---- 到货 ----
|
||||
handleUploadSuccess(res) {
|
||||
if (res.code === 200) {
|
||||
const data = res.data || {}
|
||||
if (data.kgConverted) {
|
||||
this.$alert(res.msg, '导入完成(含单位纠正)', { dangerouslyUseHTMLString: true, type: 'warning' })
|
||||
} else {
|
||||
this.$modal.msgSuccess(res.msg || '导入成功')
|
||||
}
|
||||
this.refreshDetail()
|
||||
this.getList(true)
|
||||
} else {
|
||||
this.$alert(res.msg || '导入失败', '到货文件校验未通过', { dangerouslyUseHTMLString: true, type: 'error' })
|
||||
}
|
||||
},
|
||||
handleUploadError() {
|
||||
this.$modal.msgError('上传失败,请检查文件后重试')
|
||||
},
|
||||
removeDelivery(row) {
|
||||
this.$modal.confirm('确定删除该到货记录吗?').then(() => {
|
||||
return delDelivery(row.deliveryId)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
this.refreshDetail()
|
||||
this.getList(true)
|
||||
}).catch(() => {})
|
||||
},
|
||||
auditText(s) {
|
||||
return { '0': '待审核', '1': '已通过', '2': '已驳回', '3': '待送审' }[s] || '—'
|
||||
},
|
||||
itemStatusText(s) {
|
||||
return { '0': '未到货', '1': '部分到货', '2': '已到货' }[s] || '未到货'
|
||||
return s === '2' ? '已到货' : '未到货'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,9 @@
|
||||
<el-table-column label="联系电话" prop="contactPhone" width="130" />
|
||||
<el-table-column label="地址" prop="address" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="备注" prop="remark" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="130" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="190" align="center" fixed="right">
|
||||
<template slot-scope="s">
|
||||
<el-button type="text" size="mini" icon="el-icon-notebook-2" @click="openTxn(s.row)" v-hasPermi="['erp:supplier:list']">往来</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleUpdate(s.row)" v-hasPermi="['erp:supplier:edit']">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDelete(s.row)" v-hasPermi="['erp:supplier:remove']">删除</el-button>
|
||||
</template>
|
||||
@@ -115,11 +116,67 @@
|
||||
<el-button type="primary" :loading="buttonLoading" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 往来流水台账 -->
|
||||
<el-dialog :title="'往来流水 · ' + (txnSupplier.name || '')" :visible.sync="txnOpen" width="860px" append-to-body>
|
||||
<div class="sp-txn-sum">
|
||||
<div class="sp-sum-i"><label>采购应付合计</label><span>{{ fmt(txnSum.payable) }}</span></div>
|
||||
<div class="sp-sum-i"><label>付款合计</label><span>{{ fmt(txnSum.paid) }}</span></div>
|
||||
<div class="sp-sum-i"><label>退货合计</label><span>{{ fmt(txnSum.returned) }}</span></div>
|
||||
<div class="sp-sum-i bal"><label>当前应付余额</label><span :class="{ neg: Number(txnSum.balance) < 0 }">{{ fmt(txnSum.balance) }}</span></div>
|
||||
</div>
|
||||
|
||||
<el-form :model="txnForm" :inline="true" size="small" class="sp-txn-form">
|
||||
<el-form-item label="日期">
|
||||
<el-date-picker v-model="txnForm.txnDate" type="date" value-format="yyyy-MM-dd" placeholder="日期" style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型">
|
||||
<el-select v-model="txnForm.txnType" style="width:120px">
|
||||
<el-option v-for="t in txnTypeOptions" :key="t.value" :label="t.label" :value="t.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="金额">
|
||||
<el-input v-model="txnForm.amount" placeholder="金额" style="width:120px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单据号">
|
||||
<el-input v-model="txnForm.docNo" placeholder="单据号" style="width:130px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="txnForm.remark" placeholder="备注" style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="mini" icon="el-icon-plus" :loading="txnSaving" @click="addTxn">记一笔</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="txnList" border size="mini" max-height="360" v-loading="txnLoading">
|
||||
<el-table-column label="日期" prop="txnDate" width="110" align="center" />
|
||||
<el-table-column label="类型" width="90" align="center">
|
||||
<template slot-scope="s"><span class="sp-txn-tag" :class="'t' + s.row.txnType">{{ txnTypeText(s.row.txnType) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" prop="amount" width="110" align="right">
|
||||
<template slot-scope="s">{{ fmt(s.row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应付余额" prop="balance" width="120" align="right">
|
||||
<template slot-scope="s"><span :class="{ neg: Number(s.row.balance) < 0 }">{{ fmt(s.row.balance) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单据号" prop="docNo" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="备注" prop="remark" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="录入人" prop="createBy" width="90" align="center" />
|
||||
<el-table-column label="操作" width="60" align="center">
|
||||
<template slot-scope="s"><i class="el-icon-delete sp-del" @click="delTxn(s.row)" /></template>
|
||||
</el-table-column>
|
||||
<template slot="empty"><span>暂无往来记录</span></template>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listSupplier, addSupplier, updateSupplier, delSupplier } from '@/api/erp/purchase'
|
||||
import {
|
||||
listSupplier, addSupplier, updateSupplier, delSupplier,
|
||||
listSupplierTxn, supplierTxnSummary, addSupplierTxn, delSupplierTxn
|
||||
} from '@/api/erp/purchase'
|
||||
|
||||
export default {
|
||||
name: 'ErpSupplier',
|
||||
@@ -145,7 +202,21 @@ export default {
|
||||
rules: {
|
||||
supplierCode: [{ required: true, message: '请输入供应商编码', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '请输入供应商名称', trigger: 'blur' }]
|
||||
}
|
||||
},
|
||||
// 往来流水
|
||||
txnOpen: false,
|
||||
txnLoading: false,
|
||||
txnSaving: false,
|
||||
txnSupplier: {},
|
||||
txnList: [],
|
||||
txnSum: { payable: 0, paid: 0, returned: 0, balance: 0 },
|
||||
txnForm: { txnDate: '', txnType: '1', amount: '', docNo: '', remark: '' },
|
||||
txnTypeOptions: [
|
||||
{ value: '1', label: '采购应付' },
|
||||
{ value: '2', label: '付款' },
|
||||
{ value: '3', label: '退货' },
|
||||
{ value: '4', label: '其他' }
|
||||
]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -215,6 +286,55 @@ export default {
|
||||
typeText(t) {
|
||||
const o = this.typeOptions.find(x => x.value === t)
|
||||
return o ? o.label : (t || '—')
|
||||
},
|
||||
// ---- 往来流水 ----
|
||||
openTxn(row) {
|
||||
this.txnSupplier = { ...row }
|
||||
this.txnForm = { txnDate: this.today(), txnType: '1', amount: '', docNo: '', remark: '' }
|
||||
this.txnOpen = true
|
||||
this.loadTxn()
|
||||
},
|
||||
loadTxn() {
|
||||
const id = this.txnSupplier.supplierId
|
||||
this.txnLoading = true
|
||||
listSupplierTxn(id).then(res => { this.txnList = res.data || [] }).finally(() => { this.txnLoading = false })
|
||||
supplierTxnSummary(id).then(res => { this.txnSum = res.data || { payable: 0, paid: 0, returned: 0, balance: 0 } })
|
||||
},
|
||||
addTxn() {
|
||||
if (this.txnForm.amount === '' || isNaN(Number(this.txnForm.amount))) {
|
||||
this.$modal.msgWarning('请输入正确的金额')
|
||||
return
|
||||
}
|
||||
this.txnSaving = true
|
||||
addSupplierTxn({ ...this.txnForm, supplierId: this.txnSupplier.supplierId }).then(() => {
|
||||
this.$modal.msgSuccess('已记账')
|
||||
this.txnForm.amount = ''
|
||||
this.txnForm.docNo = ''
|
||||
this.txnForm.remark = ''
|
||||
this.loadTxn()
|
||||
}).finally(() => { this.txnSaving = false })
|
||||
},
|
||||
delTxn(row) {
|
||||
this.$modal.confirm('确认删除该往来记录?').then(() => {
|
||||
return delSupplierTxn(row.txnId)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
this.loadTxn()
|
||||
}).catch(() => {})
|
||||
},
|
||||
txnTypeText(t) {
|
||||
const o = this.txnTypeOptions.find(x => x.value === t)
|
||||
return o ? o.label : '—'
|
||||
},
|
||||
fmt(v) {
|
||||
const n = Number(v || 0)
|
||||
return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
},
|
||||
today() {
|
||||
const d = new Date()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${m}-${day}`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,4 +349,22 @@ $accent: #5b8db8;
|
||||
&.cC { color: #d6a256; }
|
||||
&.cD { color: #c45656; }
|
||||
}
|
||||
|
||||
/* 往来流水 */
|
||||
.sp-txn-sum { display: flex; gap: 10px; margin-bottom: 12px; }
|
||||
.sp-sum-i {
|
||||
flex: 1; border: 1px solid #e4e7ed; border-radius: 3px; padding: 8px 12px; background: #fafbfc;
|
||||
label { display: block; color: #909399; font-size: 12px; margin-bottom: 4px; }
|
||||
span { font-size: 16px; font-weight: 600; color: #303133; }
|
||||
&.bal { background: #eef3f8; border-color: #b9d2e6; span { color: $accent; } }
|
||||
span.neg { color: #3a8a4d; }
|
||||
}
|
||||
.sp-txn-form { margin-bottom: 4px; ::v-deep .el-form-item { margin-bottom: 8px; } }
|
||||
.sp-txn-tag {
|
||||
font-size: 11px; line-height: 16px; padding: 0 6px; border-radius: 2px; border: 1px solid #dcdfe6; color: #909399;
|
||||
&.t1 { color: #d6a256; border-color: #ecd4a6; background: #fdf6ec; }
|
||||
&.t2 { color: #3a8a4d; border-color: #b7d9bf; background: #f0f9f1; }
|
||||
&.t3 { color: #c45656; border-color: #e6c4c4; background: #fbf0f0; }
|
||||
}
|
||||
.sp-del { color: #c45656; cursor: pointer; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user