销售发货,产品调整

This commit is contained in:
朱昊天
2026-05-21 14:22:42 +08:00
parent 264ca0e407
commit 7686d70e59
46 changed files with 3250 additions and 489 deletions

View File

@@ -0,0 +1,2 @@
DEBUG_SERVER_URL=http://127.0.0.1:7777/event
DEBUG_SESSION_ID=product-import-image

View File

@@ -0,0 +1,36 @@
{"ts": 1779294580277, "event": "import_open_workbook", "payload": {"fileName": "询价-产品目录整理2604.xls", "fileSize": 204246016, "workbookClass": "org.apache.poi.hssf.usermodel.HSSFWorkbook"}}
{"ts": 1779294580300, "event": "import_header", "payload": {"fileName": "询价-产品目录整理2604.xls", "sheetClass": "org.apache.poi.hssf.usermodel.HSSFSheet", "lastRowNum": 333, "headerIndex": {"序号": 0, "产品名称": 1, "产品图片": 2, "产品规格": 3, "价格": 4, "产品型号": 5}, "colProductName": 1, "colSpec": 3, "colModel": 5, "colUnitPrice": -1, "colRemark": -1, "colImages": 2, "xssfCellImagesPart": null}}
{"ts": 1779294580378, "event": "import_pictures_map", "payload": {"fileName": "询价-产品目录整理2604.xls", "rowsWithPictures": 56, "totalPictures": 65}}
{"ts": 1779294580412, "event": "import_image_miss_row", "payload": {"rowIndex": 1, "productName": "床头柜", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_DE28F1DE70D7472299A0EB95E12ABDE8\",1)"}}
{"ts": 1779294580415, "event": "import_image_miss_row", "payload": {"rowIndex": 2, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_D1DA8BA74D034AC0892AF25D577A3DFC\",1)"}}
{"ts": 1779294580417, "event": "import_image_miss_row", "payload": {"rowIndex": 3, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_C5A9E5DB6F484CD4B0ED9E78B40B850B\",1)"}}
{"ts": 1779294580420, "event": "import_image_miss_row", "payload": {"rowIndex": 4, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_E9A371D0A3D74FE8AFF9840431B05F3E\",1)"}}
{"ts": 1779294580423, "event": "import_image_miss_row", "payload": {"rowIndex": 5, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_690D368FCF9F4BFAB19C8654A6C61FFD\",1)"}}
{"ts": 1779294642453, "event": "import_done_parse", "payload": {"fileName": "询价-产品目录整理2604.xls", "resultSize": 236, "rowsWithProductImages": 55}}
{"ts": 1779294704871, "event": "import_open_workbook", "payload": {"fileName": "询价-产品目录整理2604.xls", "fileSize": 204246016, "workbookClass": "org.apache.poi.hssf.usermodel.HSSFWorkbook"}}
{"ts": 1779294704873, "event": "import_header", "payload": {"fileName": "询价-产品目录整理2604.xls", "sheetClass": "org.apache.poi.hssf.usermodel.HSSFSheet", "lastRowNum": 333, "headerIndex": {"序号": 0, "产品名称": 1, "产品图片": 2, "产品规格": 3, "价格": 4, "产品型号": 5}, "colProductName": 1, "colSpec": 3, "colModel": 5, "colUnitPrice": -1, "colRemark": -1, "colImages": 2, "xssfCellImagesPart": null}}
{"ts": 1779294704910, "event": "import_pictures_map", "payload": {"fileName": "询价-产品目录整理2604.xls", "rowsWithPictures": 56, "totalPictures": 65}}
{"ts": 1779294704913, "event": "import_image_miss_row", "payload": {"rowIndex": 1, "productName": "床头柜", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_DE28F1DE70D7472299A0EB95E12ABDE8\",1)"}}
{"ts": 1779294704916, "event": "import_image_miss_row", "payload": {"rowIndex": 2, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_D1DA8BA74D034AC0892AF25D577A3DFC\",1)"}}
{"ts": 1779294704918, "event": "import_image_miss_row", "payload": {"rowIndex": 3, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_C5A9E5DB6F484CD4B0ED9E78B40B850B\",1)"}}
{"ts": 1779294704921, "event": "import_image_miss_row", "payload": {"rowIndex": 4, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_E9A371D0A3D74FE8AFF9840431B05F3E\",1)"}}
{"ts": 1779294704925, "event": "import_image_miss_row", "payload": {"rowIndex": 5, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_690D368FCF9F4BFAB19C8654A6C61FFD\",1)"}}
{"ts": 1779294767240, "event": "import_done_parse", "payload": {"fileName": "询价-产品目录整理2604.xls", "resultSize": 236, "rowsWithProductImages": 55}}
{"ts": 1779294901849, "event": "import_open_workbook", "payload": {"fileName": "询价-产品目录整理2604.xls", "fileSize": 204246016, "workbookClass": "org.apache.poi.hssf.usermodel.HSSFWorkbook"}}
{"ts": 1779294901852, "event": "import_header", "payload": {"fileName": "询价-产品目录整理2604.xls", "sheetClass": "org.apache.poi.hssf.usermodel.HSSFSheet", "lastRowNum": 333, "headerIndex": {"序号": 0, "产品名称": 1, "产品图片": 2, "产品规格": 3, "价格": 4, "产品型号": 5}, "colProductName": 1, "colSpec": 3, "colModel": 5, "colUnitPrice": -1, "colRemark": -1, "colImages": 2, "xssfCellImagesPart": null}}
{"ts": 1779294901883, "event": "import_pictures_map", "payload": {"fileName": "询价-产品目录整理2604.xls", "rowsWithPictures": 56, "totalPictures": 65}}
{"ts": 1779294901886, "event": "import_image_miss_row", "payload": {"rowIndex": 1, "productName": "床头柜", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_DE28F1DE70D7472299A0EB95E12ABDE8\",1)"}}
{"ts": 1779294901889, "event": "import_image_miss_row", "payload": {"rowIndex": 2, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_D1DA8BA74D034AC0892AF25D577A3DFC\",1)"}}
{"ts": 1779294901893, "event": "import_image_miss_row", "payload": {"rowIndex": 3, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_C5A9E5DB6F484CD4B0ED9E78B40B850B\",1)"}}
{"ts": 1779294901895, "event": "import_image_miss_row", "payload": {"rowIndex": 4, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_E9A371D0A3D74FE8AFF9840431B05F3E\",1)"}}
{"ts": 1779294901897, "event": "import_image_miss_row", "payload": {"rowIndex": 5, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_690D368FCF9F4BFAB19C8654A6C61FFD\",1)"}}
{"ts": 1779294980062, "event": "import_done_parse", "payload": {"fileName": "询价-产品目录整理2604.xls", "resultSize": 236, "rowsWithProductImages": 55}}
{"ts": 1779295230641, "event": "import_open_workbook", "payload": {"fileName": "询价-产品目录整理2604.xls", "fileSize": 204246016, "workbookClass": "org.apache.poi.hssf.usermodel.HSSFWorkbook"}}
{"ts": 1779295230643, "event": "import_header", "payload": {"fileName": "询价-产品目录整理2604.xls", "sheetClass": "org.apache.poi.hssf.usermodel.HSSFSheet", "lastRowNum": 333, "headerIndex": {"序号": 0, "产品名称": 1, "产品图片": 2, "产品规格": 3, "价格": 4, "产品型号": 5}, "colProductName": 1, "colSpec": 3, "colModel": 5, "colUnitPrice": -1, "colRemark": -1, "colImages": 2, "xssfCellImagesPart": null}}
{"ts": 1779295230672, "event": "import_pictures_map", "payload": {"fileName": "询价-产品目录整理2604.xls", "rowsWithPictures": 56, "totalPictures": 65}}
{"ts": 1779295230676, "event": "import_image_miss_row", "payload": {"rowIndex": 1, "productName": "床头柜", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_DE28F1DE70D7472299A0EB95E12ABDE8\",1)"}}
{"ts": 1779295230679, "event": "import_image_miss_row", "payload": {"rowIndex": 2, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_D1DA8BA74D034AC0892AF25D577A3DFC\",1)"}}
{"ts": 1779295230682, "event": "import_image_miss_row", "payload": {"rowIndex": 3, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_C5A9E5DB6F484CD4B0ED9E78B40B850B\",1)"}}
{"ts": 1779295230684, "event": "import_image_miss_row", "payload": {"rowIndex": 4, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_E9A371D0A3D74FE8AFF9840431B05F3E\",1)"}}
{"ts": 1779295230686, "event": "import_image_miss_row", "payload": {"rowIndex": 5, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_690D368FCF9F4BFAB19C8654A6C61FFD\",1)"}}
{"ts": 1779295298724, "event": "import_done_parse", "payload": {"fileName": "询价-产品目录整理2604.xls", "resultSize": 236, "rowsWithProductImages": 55}}

View File

@@ -76,9 +76,9 @@ spring:
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
max-file-size: 512MB
# 设置总上传的文件大小
max-request-size: 20MB
max-request-size: 512MB
# 服务模块
devtools:
restart:

View File

@@ -15,6 +15,10 @@
<groupId>com.gear</groupId>
<artifactId>gear-common</artifactId>
</dependency>
<dependency>
<groupId>com.gear</groupId>
<artifactId>gear-system</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>

View File

@@ -67,6 +67,11 @@ public class MatMaterialOutBo extends BaseEntity {
*/
private String remark;
/**
* 厂家(配料厂家)
*/
private String factory;
/**
* 开始时间
*/

View File

@@ -27,4 +27,7 @@ public class MatProductImportBo {
@ExcelProperty("备注")
private String remark;
@ExcelProperty("产品图片")
private String productImages;
}

View File

@@ -72,6 +72,11 @@ public class MatPurchaseInDetailBo extends BaseEntity {
*/
private String remark;
/**
* 厂家(配料厂家)
*/
private String factory;
/**
* 开始时间

View File

@@ -71,6 +71,7 @@ public class MatMaterialOutServiceImpl implements IMatMaterialOutService {
lqw.eq(StringUtils.isNotBlank(bo.getOperator()), "mmo.operator", bo.getOperator());
lqw.ge(bo.getBeginTime() != null, "mmo.out_time", bo.getBeginTime());
lqw.le(bo.getEndTime() != null, "mmo.out_time", bo.getEndTime());
lqw.eq(StringUtils.isNotBlank(bo.getFactory()), "mm.factory", bo.getFactory());
// 逻辑删除
lqw.eq("mmo.del_flag", 0);
// 排序

View File

@@ -255,6 +255,10 @@ public class MatProductServiceImpl implements IMatProductService {
data.setModel(model);
data.setUnitPrice(row.getUnitPrice());
data.setRemark(row.getRemark());
String images = StringUtils.trim(row.getProductImages());
if (StringUtils.isNotBlank(images)) {
data.setProductImages(images);
}
data.setProductType(normalizeProductType(data.getProductType()));
data.setCurrentStock(normalizeCurrentStock(data.getCurrentStock()));
if (exist == null) {

View File

@@ -85,6 +85,7 @@ public class MatPurchaseInDetailServiceImpl implements IMatPurchaseInDetailServi
qw.ge(bo.getBeginTime() != null, "mpid.in_time", bo.getBeginTime());
qw.le(bo.getEndTime() != null, "mpid.in_time", bo.getEndTime());
qw.eq(StringUtils.isNotBlank(bo.getOperator()), "mpid.operator", bo.getOperator());
qw.eq(StringUtils.isNotBlank(bo.getFactory()), "mm.factory", bo.getFactory());
// 逻辑删除
qw.eq("mpid.del_flag", 0);
// 排序

View File

@@ -0,0 +1,60 @@
package com.gear.oa.controller;
import com.gear.common.annotation.Log;
import com.gear.common.annotation.RepeatSubmit;
import com.gear.common.core.controller.BaseController;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.domain.R;
import com.gear.common.core.page.TableDataInfo;
import com.gear.common.core.validate.AddGroup;
import com.gear.common.core.validate.EditGroup;
import com.gear.common.enums.BusinessType;
import com.gear.oa.domain.bo.GearShippingOrderDetailBo;
import com.gear.oa.domain.vo.GearShippingOrderDetailVo;
import com.gear.oa.service.IGearShippingOrderDetailService;
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;
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/oa/shippingOrderDetail")
public class GearShippingOrderDetailController extends BaseController {
private final IGearShippingOrderDetailService iGearShippingOrderDetailService;
@GetMapping("/list")
public TableDataInfo<GearShippingOrderDetailVo> list(GearShippingOrderDetailBo bo, PageQuery pageQuery) {
return iGearShippingOrderDetailService.queryPageList(bo, pageQuery);
}
@GetMapping("/{detailId}")
public R<GearShippingOrderDetailVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long detailId) {
return R.ok(iGearShippingOrderDetailService.queryById(detailId));
}
@Log(title = "发货单据明细", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody GearShippingOrderDetailBo bo) {
return toAjax(iGearShippingOrderDetailService.insertByBo(bo));
}
@Log(title = "发货单据明细", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody GearShippingOrderDetailBo bo) {
return toAjax(iGearShippingOrderDetailService.updateByBo(bo));
}
@Log(title = "发货单据明细", businessType = BusinessType.DELETE)
@DeleteMapping("/{detailIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] detailIds) {
return toAjax(iGearShippingOrderDetailService.deleteWithValidByIds(Arrays.asList(detailIds), true));
}
}

View File

@@ -9,6 +9,8 @@ import com.gear.common.core.page.TableDataInfo;
import com.gear.common.core.validate.AddGroup;
import com.gear.common.core.validate.EditGroup;
import com.gear.common.enums.BusinessType;
import com.gear.common.exception.ServiceException;
import com.gear.common.utils.StringUtils;
import com.gear.common.utils.poi.ExcelUtil;
import com.gear.oa.domain.bo.GearStockIoOrderBo;
import com.gear.oa.domain.bo.GearStockIoOrderWithDetailBo;
@@ -54,23 +56,39 @@ public class GearStockIoOrderController extends BaseController {
}
@GetMapping("/materialFlow")
public R<IGearStockIoOrderService.MaterialFlowResp> materialFlow(@NotNull(message = "物料ID不能为空") @RequestParam Long itemId,
public R<IGearStockIoOrderService.MaterialFlowResp> materialFlow(@RequestParam(required = false) Long itemId,
@RequestParam(required = false) String factory,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime) {
return R.ok(stockIoOrderService.queryMaterialFlow(itemId, startTime, endTime));
if (itemId != null) {
return R.ok(stockIoOrderService.queryMaterialFlow(itemId, startTime, endTime));
}
if (StringUtils.isBlank(factory)) {
return R.fail("请选择物料或填写厂家");
}
return R.ok(stockIoOrderService.queryMaterialFlowByFactory(factory, startTime, endTime));
}
@Log(title = "物料出入库统计", businessType = BusinessType.EXPORT)
@PostMapping("/materialFlow/export")
public void exportMaterialFlow(@NotNull(message = "物料ID不能为空") @RequestParam Long itemId,
public void exportMaterialFlow(@RequestParam(required = false) Long itemId,
@RequestParam(required = false) String factory,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime,
HttpServletResponse response) {
IGearStockIoOrderService.MaterialFlowResp resp = stockIoOrderService.queryMaterialFlow(itemId, startTime, endTime);
IGearStockIoOrderService.MaterialFlowResp resp;
if (itemId != null) {
resp = stockIoOrderService.queryMaterialFlow(itemId, startTime, endTime);
} else if (StringUtils.isNotBlank(factory)) {
resp = stockIoOrderService.queryMaterialFlowByFactory(factory, startTime, endTime);
} else {
throw new ServiceException("请选择物料或填写厂家");
}
List<MaterialFlowExportRow> exportRows = new ArrayList<>();
MaterialFlowExportRow summary = new MaterialFlowExportRow();
summary.setItemId(resp.getItemId());
summary.setFactory(factory);
summary.setStartTime(resp.getStartTime());
summary.setEndTime(resp.getEndTime());
summary.setAction("汇总");
@@ -85,6 +103,7 @@ public class GearStockIoOrderController extends BaseController {
for (IGearStockIoOrderService.MaterialFlowRow r : resp.getRows()) {
MaterialFlowExportRow row = new MaterialFlowExportRow();
row.setItemId(resp.getItemId());
row.setFactory(factory);
row.setTime(r.getTime());
row.setAction(r.getAction());
row.setOrderCode(r.getOrderCode());
@@ -105,6 +124,8 @@ public class GearStockIoOrderController extends BaseController {
public static class MaterialFlowExportRow {
@ExcelProperty(value = "物料ID")
private Long itemId;
@ExcelProperty(value = "厂家")
private String factory;
@ExcelProperty(value = "开始时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@@ -144,6 +165,14 @@ public class GearStockIoOrderController extends BaseController {
this.itemId = itemId;
}
public String getFactory() {
return factory;
}
public void setFactory(String factory) {
this.factory = factory;
}
public Date getStartTime() {
return startTime;
}

View File

@@ -62,4 +62,8 @@ public class GearOrderDetail extends BaseEntity {
*/
private BigDecimal noTaxPrice;
private String spec;
private String model;
}

View File

@@ -0,0 +1,44 @@
package com.gear.oa.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.gear.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("gear_shipping_order_detail")
public class GearShippingOrderDetail extends BaseEntity {
private static final long serialVersionUID = 1L;
@TableId(value = "detail_id")
private Long detailId;
private Long shippingId;
private Long productId;
private String productCode;
private String productName;
private String spec;
private String model;
private BigDecimal quantity;
private String unit;
private String remark;
private Integer sort;
@TableLogic(value = "0", delval = "2")
private String delFlag;
}

View File

@@ -63,5 +63,9 @@ public class GearOrderDetailBo extends BaseEntity {
*/
private BigDecimal noTaxPrice;
private String spec;
private String model;
}

View File

@@ -40,6 +40,8 @@ public class GearReceivableBo extends BaseEntity {
*/
private Long orderId;
private Long salesmanId;
/**
* 到期日
*/

View File

@@ -0,0 +1,40 @@
package com.gear.oa.domain.bo;
import com.gear.common.core.domain.BaseEntity;
import com.gear.common.core.validate.AddGroup;
import com.gear.common.core.validate.EditGroup;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Data
@EqualsAndHashCode(callSuper = true)
public class GearShippingOrderDetailBo extends BaseEntity {
private Long detailId;
@NotNull(message = "发货单据ID不能为空", groups = {AddGroup.class, EditGroup.class})
private Long shippingId;
@NotNull(message = "产品ID不能为空", groups = {AddGroup.class, EditGroup.class})
private Long productId;
private String productCode;
private String productName;
private String spec;
private String model;
@NotNull(message = "数量不能为空", groups = {AddGroup.class, EditGroup.class})
private BigDecimal quantity;
private String unit;
private String remark;
private Integer sort;
}

View File

@@ -0,0 +1,38 @@
package com.gear.oa.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.gear.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@Data
@EqualsAndHashCode(callSuper = true)
@ExcelIgnoreUnannotated
public class GearShippingOrderDetailVo extends BaseEntity {
private static final long serialVersionUID = 1L;
private Long detailId;
private Long shippingId;
private Long productId;
private String productCode;
private String productName;
private String spec;
private String model;
private BigDecimal quantity;
private String unit;
private String remark;
private Integer sort;
}

View File

@@ -0,0 +1,8 @@
package com.gear.oa.mapper;
import com.gear.common.core.mapper.BaseMapperPlus;
import com.gear.oa.domain.GearShippingOrderDetail;
import com.gear.oa.domain.vo.GearShippingOrderDetailVo;
public interface GearShippingOrderDetailMapper extends BaseMapperPlus<GearShippingOrderDetailMapper, GearShippingOrderDetail, GearShippingOrderDetailVo> {
}

View File

@@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
public interface MatMaterialSimpleMapper {
@@ -13,7 +14,9 @@ public interface MatMaterialSimpleMapper {
"from mat_material where material_id = #{materialId} and del_flag = 0 limit 1")
Map<String, Object> selectSnapshot(@Param("materialId") Long materialId);
@Select("select material_id from mat_material where del_flag = 0 and factory like concat('%', #{factory}, '%')")
List<Long> selectIdsByFactory(@Param("factory") String factory);
@Update("update mat_material set current_stock = ifnull(current_stock, 0) + #{delta} where material_id = #{materialId} and del_flag = 0")
int updateStockDelta(@Param("materialId") Long materialId, @Param("delta") BigDecimal delta);
}

View File

@@ -0,0 +1,24 @@
package com.gear.oa.service;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.page.TableDataInfo;
import com.gear.oa.domain.bo.GearShippingOrderDetailBo;
import com.gear.oa.domain.vo.GearShippingOrderDetailVo;
import java.util.Collection;
import java.util.List;
public interface IGearShippingOrderDetailService {
GearShippingOrderDetailVo queryById(Long detailId);
TableDataInfo<GearShippingOrderDetailVo> queryPageList(GearShippingOrderDetailBo bo, PageQuery pageQuery);
List<GearShippingOrderDetailVo> queryList(GearShippingOrderDetailBo bo);
Boolean insertByBo(GearShippingOrderDetailBo bo);
Boolean updateByBo(GearShippingOrderDetailBo bo);
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@@ -35,6 +35,8 @@ public interface IGearStockIoOrderService {
MaterialFlowResp queryMaterialFlow(Long itemId, Date startTime, Date endTime);
MaterialFlowResp queryMaterialFlowByFactory(String factory, Date startTime, Date endTime);
@lombok.Data
class MaterialFlowResp {
private Long itemId;

View File

@@ -13,9 +13,7 @@ import org.springframework.stereotype.Service;
import com.gear.oa.domain.bo.GearOrderBo;
import com.gear.oa.domain.vo.GearOrderVo;
import com.gear.oa.domain.GearOrder;
import com.gear.oa.domain.GearShippingOrder;
import com.gear.oa.mapper.GearOrderMapper;
import com.gear.oa.mapper.GearShippingOrderMapper;
import com.gear.oa.service.IGearOrderService;
import java.util.List;
@@ -33,51 +31,13 @@ import java.util.Collection;
public class GearOrderServiceImpl implements IGearOrderService {
private final GearOrderMapper baseMapper;
private final GearShippingOrderMapper shippingOrderMapper;
/**
* 查询订单主
*/
@Override
public GearOrderVo queryById(Long orderId){
GearOrderVo vo = baseMapper.selectVoById(orderId);
if (vo == null) {
return null;
}
Integer derived = deriveOrderStatusByShipping(orderId, vo.getOrderStatus());
if (derived != null && vo.getOrderStatus() != null && !derived.equals(vo.getOrderStatus())) {
baseMapper.update(null, Wrappers.<GearOrder>lambdaUpdate()
.eq(GearOrder::getOrderId, orderId)
.ne(GearOrder::getOrderStatus, 3)
.in(GearOrder::getOrderStatus, 0, 1)
.set(GearOrder::getOrderStatus, derived));
vo.setOrderStatus(derived);
}
return vo;
}
private Integer deriveOrderStatusByShipping(Long orderId, Integer currentStatus) {
if (orderId == null) return currentStatus;
if (currentStatus != null && currentStatus == 3) return 3;
QueryWrapper<GearShippingOrder> qw = new QueryWrapper<>();
qw.select("MAX(CAST(status AS SIGNED))");
qw.eq("order_id", orderId);
qw.eq("del_flag", "0");
List<Object> objs = shippingOrderMapper.selectObjs(qw);
Integer maxStatus = null;
if (objs != null && !objs.isEmpty() && objs.get(0) != null) {
try {
maxStatus = Integer.parseInt(String.valueOf(objs.get(0)));
} catch (Exception ignored) {
maxStatus = null;
}
}
int st = currentStatus == null ? 0 : currentStatus;
if (maxStatus != null && maxStatus >= 3 && (st == 0 || st == 1)) return 2;
if (maxStatus != null && maxStatus >= 2 && st == 0) return 1;
return currentStatus;
return baseMapper.selectVoById(orderId);
}
/**
@@ -98,8 +58,6 @@ public class GearOrderServiceImpl implements IGearOrderService {
lqw.eq(bo.getSalesmanId() != null, "o.salesman_id", bo.getSalesmanId());
lqw.like(StringUtils.isNotBlank(bo.getSalesManager()), "s.name", bo.getSalesManager());
lqw.eq(bo.getOrderStatus() != null, "o.order_status", bo.getOrderStatus());
lqw.apply(bo.getOrderStatus() != null && bo.getOrderStatus() == 0,
"NOT EXISTS (SELECT 1 FROM gear_shipping_order so WHERE so.order_id = o.order_id AND so.del_flag = '0' AND CAST(so.status AS SIGNED) >= 2)");
lqw.eq(bo.getTradeType() != null, "o.trade_type", bo.getTradeType());
lqw.eq(bo.getTaxAmount() != null, "o.tax_amount", bo.getTaxAmount());
lqw.eq(bo.getNoTaxAmount() != null, "o.no_tax_amount", bo.getNoTaxAmount());

View File

@@ -9,8 +9,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.gear.oa.domain.GearJournal;
import com.gear.oa.domain.GearOrder;
import com.gear.oa.mapper.GearCustomerMapper;
import com.gear.oa.mapper.GearJournalMapper;
import com.gear.oa.mapper.GearOrderMapper;
import com.gear.oa.service.IGearJournalService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -24,9 +26,11 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.stream.Collectors;
/**
* 应收款管理宽松版Service业务层处理
@@ -39,6 +43,7 @@ import java.util.Collection;
public class GearReceivableServiceImpl implements IGearReceivableService {
private final GearReceivableMapper baseMapper;
private final GearOrderMapper orderMapper;
@Resource
private GearJournalMapper journalMapper;
@Resource
@@ -67,7 +72,16 @@ public class GearReceivableServiceImpl implements IGearReceivableService {
QueryWrapper<GearReceivable> lqw = Wrappers.query();
lqw.eq("r.del_flag", 0);
lqw.eq(bo.getCustomerId() != null, "r.customer_id", bo.getCustomerId());
lqw.eq(bo.getOrderId() != null, "r.order_id", bo.getOrderId());
if (bo.getOrderId() != null) {
lqw.eq("r.order_id", bo.getOrderId());
} else if (bo.getSalesmanId() != null) {
List<Long> orderIds = queryOrderIdsBySalesman(bo.getSalesmanId());
if (orderIds.isEmpty()) {
lqw.eq("r.receivable_id", -1L);
} else {
lqw.in("r.order_id", orderIds);
}
}
lqw.eq(bo.getDueDate() != null, "r.due_date", bo.getDueDate());
lqw.eq(bo.getAmount() != null, "r.amount", bo.getAmount());
lqw.eq(bo.getPaidAmount() != null, "r.paid_amount", bo.getPaidAmount());
@@ -77,6 +91,19 @@ public class GearReceivableServiceImpl implements IGearReceivableService {
lqw.between(bo.getStartTime() != null && bo.getEndTime() != null, "r.create_time", bo.getStartTime(), bo.getEndTime());
return lqw;
}
private List<Long> queryOrderIdsBySalesman(Long salesmanId) {
if (salesmanId == null) return Collections.emptyList();
LambdaQueryWrapper<GearOrder> lqw = Wrappers.lambdaQuery();
lqw.select(GearOrder::getOrderId);
lqw.eq(GearOrder::getSalesmanId, salesmanId);
lqw.eq(GearOrder::getDelFlag, 0);
List<GearOrder> list = orderMapper.selectList(lqw);
if (list == null || list.isEmpty()) {
return Collections.emptyList();
}
return list.stream().map(GearOrder::getOrderId).filter(id -> id != null).distinct().collect(Collectors.toList());
}
/**
* 查询应收款管理(宽松版)列表
*/

View File

@@ -0,0 +1,94 @@
package com.gear.oa.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.page.TableDataInfo;
import com.gear.common.utils.StringUtils;
import com.gear.oa.domain.GearShippingOrderDetail;
import com.gear.oa.domain.bo.GearShippingOrderDetailBo;
import com.gear.oa.domain.vo.GearShippingOrderDetailVo;
import com.gear.oa.mapper.GearShippingOrderDetailMapper;
import com.gear.oa.service.IGearShippingOrderDetailService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
@RequiredArgsConstructor
@Service
public class GearShippingOrderDetailServiceImpl implements IGearShippingOrderDetailService {
private final GearShippingOrderDetailMapper baseMapper;
@Override
public GearShippingOrderDetailVo queryById(Long detailId) {
return baseMapper.selectVoById(detailId);
}
@Override
public TableDataInfo<GearShippingOrderDetailVo> queryPageList(GearShippingOrderDetailBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<GearShippingOrderDetail> lqw = buildQueryWrapper(bo);
Page<GearShippingOrderDetailVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
@Override
public List<GearShippingOrderDetailVo> queryList(GearShippingOrderDetailBo bo) {
LambdaQueryWrapper<GearShippingOrderDetail> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<GearShippingOrderDetail> buildQueryWrapper(GearShippingOrderDetailBo bo) {
LambdaQueryWrapper<GearShippingOrderDetail> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getDetailId() != null, GearShippingOrderDetail::getDetailId, bo.getDetailId());
lqw.eq(bo.getShippingId() != null, GearShippingOrderDetail::getShippingId, bo.getShippingId());
lqw.eq(bo.getProductId() != null, GearShippingOrderDetail::getProductId, bo.getProductId());
lqw.like(StringUtils.isNotBlank(bo.getProductName()), GearShippingOrderDetail::getProductName, bo.getProductName());
lqw.orderByAsc(GearShippingOrderDetail::getSort);
lqw.orderByAsc(GearShippingOrderDetail::getCreateTime);
return lqw;
}
@Override
public Boolean insertByBo(GearShippingOrderDetailBo bo) {
GearShippingOrderDetail add = BeanUtil.toBean(bo, GearShippingOrderDetail.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setDetailId(add.getDetailId());
}
return flag;
}
@Override
public Boolean updateByBo(GearShippingOrderDetailBo bo) {
GearShippingOrderDetail update = BeanUtil.toBean(bo, GearShippingOrderDetail.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
private void validEntityBeforeSave(GearShippingOrderDetail entity) {
boolean isInsert = entity.getDetailId() == null;
if (entity.getDetailId() == null) {
entity.setDetailId(IdUtil.getSnowflakeNextId());
}
if (isInsert) {
if (StringUtils.isBlank(entity.getDelFlag())) {
entity.setDelFlag("0");
}
if (entity.getSort() == null) {
entity.setSort(0);
}
}
}
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
return baseMapper.deleteBatchIds(ids) > 0;
}
}

View File

@@ -112,7 +112,6 @@ public class GearShippingOrderServiceImpl implements IGearShippingOrderService {
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setShippingId(add.getShippingId());
syncOrderStatusIfShipped(add);
}
return flag;
}
@@ -122,43 +121,9 @@ public class GearShippingOrderServiceImpl implements IGearShippingOrderService {
GearShippingOrder update = BeanUtil.toBean(bo, GearShippingOrder.class);
validEntityBeforeSave(update);
boolean ok = baseMapper.updateById(update) > 0;
if (ok) {
syncOrderStatusIfShipped(update);
}
return ok;
}
private void syncOrderStatusIfShipped(GearShippingOrder entity) {
if (entity == null || entity.getOrderId() == null) return;
Integer statusNum = null;
try {
if (StringUtils.isNotBlank(entity.getStatus())) {
statusNum = Integer.parseInt(entity.getStatus());
}
} catch (Exception ignored) {
statusNum = null;
}
if (statusNum == null) return;
// 发货单据状态与订单状态联动:
// - 发货单据 >=2已发货若订单仍为预订单(0),推进到进行中(1)
// - 发货单据 >=3已完成若订单为预订单(0)/进行中(1),推进到已完成(2)
// - 订单若已取消(3),不做推进
if (statusNum >= 3) {
orderMapper.update(null, Wrappers.<GearOrder>lambdaUpdate()
.eq(GearOrder::getOrderId, entity.getOrderId())
.ne(GearOrder::getOrderStatus, 3)
.in(GearOrder::getOrderStatus, 0, 1)
.set(GearOrder::getOrderStatus, 2));
return;
}
if (statusNum >= 2) {
orderMapper.update(null, Wrappers.<GearOrder>lambdaUpdate()
.eq(GearOrder::getOrderId, entity.getOrderId())
.eq(GearOrder::getOrderStatus, 0)
.set(GearOrder::getOrderStatus, 1));
}
}
private void validEntityBeforeSave(GearShippingOrder entity) {
boolean isInsert = entity.getShippingId() == null;
// 发货单据ID项目内大部分业务表使用雪花ID这里保持一致

View File

@@ -362,6 +362,32 @@ public class GearStockIoOrderServiceImpl implements IGearStockIoOrderService {
if (itemId == null) {
throw new ServiceException("物料ID不能为空");
}
return queryMaterialFlowByItemIds(java.util.Collections.singletonList(itemId), itemId, startTime, endTime);
}
@Override
public MaterialFlowResp queryMaterialFlowByFactory(String factory, Date startTime, Date endTime) {
if (StringUtils.isBlank(factory)) {
throw new ServiceException("厂家不能为空");
}
List<Long> ids = matMaterialMapper.selectIdsByFactory(factory);
if (ids == null || ids.isEmpty()) {
MaterialFlowResp resp = new MaterialFlowResp();
resp.setItemId(null);
resp.setStartTime(startTime);
resp.setEndTime(endTime);
resp.setConfirmInQty(BigDecimal.ZERO);
resp.setOutQty(BigDecimal.ZERO);
resp.setRevokeInQty(BigDecimal.ZERO);
resp.setRevokeOutQty(BigDecimal.ZERO);
resp.setNetQty(BigDecimal.ZERO);
resp.setRows(new ArrayList<>());
return resp;
}
return queryMaterialFlowByItemIds(ids, null, startTime, endTime);
}
private MaterialFlowResp queryMaterialFlowByItemIds(List<Long> itemIds, Long singleItemId, Date startTime, Date endTime) {
List<GearStockIoOrder> confirmOrders = baseMapper.selectList(Wrappers.<GearStockIoOrder>lambdaQuery()
.eq(GearStockIoOrder::getIoType, "I")
.eq(GearStockIoOrder::getConfirmInFlag, "1")
@@ -400,7 +426,7 @@ public class GearStockIoOrderServiceImpl implements IGearStockIoOrderService {
List<GearStockIoOrderDetail> details = detailMapper.selectList(Wrappers.<GearStockIoOrderDetail>lambdaQuery()
.in(GearStockIoOrderDetail::getOrderId, orderIds)
.eq(GearStockIoOrderDetail::getItemType, "material")
.eq(GearStockIoOrderDetail::getItemId, itemId)
.in(GearStockIoOrderDetail::getItemId, itemIds)
.eq(GearStockIoOrderDetail::getDelFlag, "0"));
for (GearStockIoOrderDetail d : details) {
if (d == null || d.getOrderId() == null) continue;
@@ -468,7 +494,7 @@ public class GearStockIoOrderServiceImpl implements IGearStockIoOrderService {
BigDecimal net = confirmInQty.subtract(outQty).add(revokeOutQty).subtract(revokeInQty);
MaterialFlowResp resp = new MaterialFlowResp();
resp.setItemId(itemId);
resp.setItemId(singleItemId);
resp.setStartTime(startTime);
resp.setEndTime(endTime);
resp.setConfirmInQty(confirmInQty);

View File

@@ -18,6 +18,8 @@
<result property="updateBy" column="update_by"/>
<result property="taxPrice" column="tax_price"/>
<result property="noTaxPrice" column="no_tax_price"/>
<result property="spec" column="spec"/>
<result property="model" column="model"/>
</resultMap>
<select id="selectVoListByOrderId" resultType="com.gear.oa.domain.vo.GearOrderDetailVo">
SELECT
@@ -25,8 +27,8 @@
COALESCE(p.product_name, mp.product_name) AS productName,
COALESCE(p.product_code, CAST(mp.product_id AS CHAR)) AS productCode,
COALESCE(p.type, mp.product_type) AS productType,
mp.spec AS spec,
mp.model AS model,
COALESCE(d.spec, mp.spec) AS spec,
COALESCE(d.model, mp.model) AS model,
mp.unit_price AS unitPrice
FROM gear_order_detail d
LEFT JOIN gear_product p ON d.product_id = p.product_id
@@ -39,8 +41,8 @@
COALESCE(p.product_name, mp.product_name) AS productName,
COALESCE(p.product_code, CAST(mp.product_id AS CHAR)) AS productCode,
COALESCE(p.type, mp.product_type) AS productType,
mp.spec AS spec,
mp.model AS model,
COALESCE(d.spec, mp.spec) AS spec,
COALESCE(d.model, mp.model) AS model,
mp.unit_price AS unitPrice
FROM gear_order_detail d
LEFT JOIN gear_product p ON d.product_id = p.product_id

View File

@@ -30,20 +30,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
o.company,
o.salesman_id,
s.name as sales_manager,
(CASE
WHEN o.order_status = 3 THEN 3
WHEN EXISTS (SELECT 1
FROM gear_shipping_order so
WHERE so.order_id = o.order_id
AND so.del_flag = '0'
AND CAST(so.status AS SIGNED) >= 3) THEN 2
WHEN EXISTS (SELECT 1
FROM gear_shipping_order so
WHERE so.order_id = o.order_id
AND so.del_flag = '0'
AND CAST(so.status AS SIGNED) >= 2) THEN 1
ELSE o.order_status
END) AS order_status,
o.order_status AS order_status,
o.remark,
o.del_flag,
o.create_time,
@@ -83,20 +70,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
o.company,
o.salesman_id,
s.name as sales_manager,
(CASE
WHEN o.order_status = 3 THEN 3
WHEN EXISTS (SELECT 1
FROM gear_shipping_order so
WHERE so.order_id = o.order_id
AND so.del_flag = '0'
AND CAST(so.status AS SIGNED) >= 3) THEN 2
WHEN EXISTS (SELECT 1
FROM gear_shipping_order so
WHERE so.order_id = o.order_id
AND so.del_flag = '0'
AND CAST(so.status AS SIGNED) >= 2) THEN 1
ELSE o.order_status
END) AS order_status,
o.order_status AS order_status,
o.remark,
o.del_flag,
o.create_time,

View File

@@ -66,7 +66,11 @@ export function importProductData(data, updateSupport) {
return request({
url: '/mat/product/importData?updateSupport=' + (updateSupport ? 1 : 0),
method: 'post',
data: data
data: data,
timeout: 1800000,
headers: {
repeatSubmit: false
}
})
}

View File

@@ -54,6 +54,48 @@ export function delShippingOrder(shippingId) {
})
}
// ================================
// 发货单据明细gear_shipping_order_detail
// ================================
export function listShippingOrderDetail(query) {
return request({
url: '/oa/shippingOrderDetail/list',
method: 'get',
params: query
})
}
export function getShippingOrderDetail(detailId) {
return request({
url: '/oa/shippingOrderDetail/' + detailId,
method: 'get'
})
}
export function addShippingOrderDetail(data) {
return request({
url: '/oa/shippingOrderDetail',
method: 'post',
data: data
})
}
export function updateShippingOrderDetail(data) {
return request({
url: '/oa/shippingOrderDetail',
method: 'put',
data: data
})
}
export function delShippingOrderDetail(detailId) {
return request({
url: '/oa/shippingOrderDetail/' + detailId,
method: 'delete'
})
}
// ================================
// 发货计划(独立表 gear_shipping_plan
// ================================

View File

@@ -36,6 +36,10 @@ service.interceptors.request.use(config => {
config.params = {}
config.url = url
}
const isFormData = typeof FormData !== 'undefined' && config.data instanceof FormData
if (isFormData) {
delete config.headers['Content-Type']
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,

View File

@@ -5,6 +5,12 @@
<el-form-item label="辅料名称" prop="materialName">
<el-input v-model="queryParams.materialName" placeholder="请输入辅料名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="规格" prop="spec">
<el-input v-model="queryParams.spec" placeholder="请输入规格" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="型号" prop="model">
<el-input v-model="queryParams.model" placeholder="请输入型号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="厂家" prop="factory">
<el-input v-model="queryParams.factory" placeholder="请输入厂家" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -40,6 +46,8 @@
<el-table-column type="selection" width="55" align="center" />
<!-- <el-table-column label="辅料ID 主键" align="center" prop="materialId" v-if="true" /> -->
<el-table-column label="辅料名称" align="center" prop="materialName" />
<el-table-column label="规格" align="center" prop="spec" min-width="140" show-overflow-tooltip />
<el-table-column label="型号" align="center" prop="model" min-width="140" show-overflow-tooltip />
<el-table-column label="物料类型" align="center" prop="materialType">
<template #default="scope">
{{ scope.row.materialType === 1 ? '辅料' : '原料' }}
@@ -75,6 +83,12 @@
<el-form-item label="辅料名称" prop="materialName">
<el-input v-model="form.materialName" placeholder="请输入辅料名称" />
</el-form-item>
<el-form-item label="规格" prop="spec">
<el-input v-model="form.spec" placeholder="请输入规格" />
</el-form-item>
<el-form-item label="型号" prop="model">
<el-input v-model="form.model" placeholder="请输入型号" />
</el-form-item>
<el-form-item label="物料类型" prop="materialType">
<el-select v-model="form.materialType" placeholder="请选择物料类型" disabled>
<el-option label="辅料" value="1" />

View File

@@ -2,6 +2,30 @@
<div class="stock-dashboard-container" style="padding: 20px;">
<!-- 加载状态 -->
<el-loading v-loading="loading" text="数据加载中...">
<el-card shadow="never" style="margin-bottom: 12px;">
<el-form :inline="true" size="small" label-width="70px">
<el-form-item label="厂家">
<el-input v-model="statQuery.factory" placeholder="请输入厂家" clearable style="width: 220px" />
</el-form-item>
<el-form-item label="时间段">
<el-date-picker
v-model="statQuery.timeRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
style="width: 360px"
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onStatSearch">查询</el-button>
<el-button @click="onStatReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 第一行4个指标卡 -->
<el-row :gutter="20" mb="20">
<el-col :span="6" v-for="item in statCards" :key="item.key">
@@ -114,6 +138,11 @@ const queryParams = reactive({
pageNum: 1,
pageSize: 10
})
const statQuery = reactive({
factory: undefined,
timeRange: undefined
})
const total = ref(0) // 材料总条数
// 格式化数值保留2位小数处理字符串转数字
@@ -144,7 +173,10 @@ const inOutCompareData = ref([]) // 出入库对比折线图数据
// 1. 获取材料列表(带分页)
const getMaterialList = async () => {
try {
const res = await listMaterial(queryParams) // 传入分页参数
const res = await listMaterial({
...queryParams,
factory: statQuery.factory || undefined
})
materialList.value = res.rows || []
total.value = res.total || 0 // 若依接口返回total总条数
} catch (err) {
@@ -153,13 +185,32 @@ const getMaterialList = async () => {
}
}
const buildStatParams = () => {
const beginTime = statQuery.timeRange && statQuery.timeRange[0] ? statQuery.timeRange[0] : undefined
const endTime = statQuery.timeRange && statQuery.timeRange[1] ? statQuery.timeRange[1] : undefined
return {
beginTime,
endTime,
factory: statQuery.factory || undefined
}
}
// 2. 获取入库+出库列表(无需分页,取全部数据做统计)
const getInOutList = async () => {
try {
const params = buildStatParams()
// 并行请求,提高效率
const [inRes, outRes] = await Promise.all([
listPurchaseInDetail(),
listMaterialOut()
listPurchaseInDetail({
pageNum: 1,
pageSize: 10000,
...params
}),
listMaterialOut({
pageNum: 1,
pageSize: 10000,
...params
})
])
purchaseInDetailList.value = inRes.rows || []
materialOutList.value = outRes.rows || []
@@ -301,6 +352,18 @@ const loadAllData = async () => {
}
}
const onStatSearch = () => {
queryParams.pageNum = 1
loadAllData()
}
const onStatReset = () => {
statQuery.factory = undefined
statQuery.timeRange = undefined
queryParams.pageNum = 1
loadAllData()
}
// 页面挂载时加载数据
onMounted(() => {
loadAllData()
@@ -318,4 +381,4 @@ onMounted(() => {
.el-card {
height: 100%;
}
</style>
</style>

View File

@@ -196,7 +196,7 @@ import { Document } from '@element-plus/icons-vue';
const router = useRouter();
const route = useRoute();
const productTypeLabel = (val) => (val === 'semi' ? '半成品' : '品');
const productTypeLabel = (val) => (val === 'semi' ? '半成品' : '品');
const productTypeTagType = (val) => (val === 'semi' ? 'warning' : 'success');
const productDetail = ref({});

View File

@@ -86,16 +86,20 @@
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="产品图片" align="center" prop="productImages">
<template #default="scope">
<div v-if="scope.row.productImages && scope.row.productImages.trim()" class="image-preview">
<div v-if="imageUrls(scope.row).length" class="image-preview">
<el-image
v-for="(image, index) in scope.row.productImages.split(',')"
:key="index"
v-for="(image, index) in imageUrls(scope.row)"
:key="image + '_' + index"
:src="image"
:preview-src-list="scope.row.productImages.split(',')"
:preview-src-list="imageUrls(scope.row)"
:z-index="9999"
:preview-teleported="true"
style="width: 32px; height: 32px;"
/>
>
<template #error>
<div class="image-error"></div>
</template>
</el-image>
</div>
<span v-else></span>
</template>
@@ -146,7 +150,7 @@
</el-form-item>
<el-form-item label="产品类型" prop="productType">
<el-select v-model="form.productType" placeholder="请选择产品类型" style="width: 100%">
<el-option label="品" value="product" />
<el-option label="品" value="product" />
<el-option label="半成品" value="semi" />
</el-select>
</el-form-item>
@@ -405,6 +409,7 @@ const updateSupport = ref(false);
const importFile = ref(null);
const additionPreviewMap = ref({});
const additionPreviewLoading = ref({});
const imagePreviewMap = ref({});
const manualOpen = ref(false);
const manualLoading = ref(false);
const manualFiles = ref([]);
@@ -430,7 +435,7 @@ const formatterTime = (time) => {
return proxy.parseTime(time, '{y}-{m}-{d}')
}
const productTypeLabel = (val) => (val === 'semi' ? '半成品' : '品');
const productTypeLabel = (val) => (val === 'semi' ? '半成品' : '品');
const productTypeTagType = (val) => (val === 'semi' ? 'warning' : 'success');
const data = reactive({
@@ -471,6 +476,7 @@ function getList() {
productList.value = response.rows;
total.value = response.total;
prefetchAdditions(productList.value);
prefetchImages(productList.value);
loading.value = false;
});
}
@@ -549,11 +555,28 @@ function handleUpdate(row) {
// 处理图片文件列表
if (form.value.productImages) {
imageFileList.value = form.value.productImages.split(',').map((url, index) => ({
name: url.substring(url.lastIndexOf('/') + 1),
url: url,
uid: Date.now() + index
}));
const raw = String(form.value.productImages).trim();
if (isOssIdList(raw)) {
listByIds(raw).then(res => {
const list = res?.data || [];
const urls = list.map(oss => oss?.url).filter(Boolean);
form.value.productImages = urls.join(',');
imageFileList.value = urls.map((url, index) => ({
name: getFileNameFromUrl(url),
url,
uid: Date.now() + index
}));
}).catch(() => {
imageFileList.value = [];
});
} else {
const urls = raw.split(',').map(s => String(s).trim()).filter(Boolean);
imageFileList.value = urls.map((url, index) => ({
name: getFileNameFromUrl(url),
url,
uid: Date.now() + index
}));
}
} else {
imageFileList.value = [];
}
@@ -884,6 +907,59 @@ function prefetchAdditions(list) {
}).catch(() => {});
}
function normalizeImageUrlList(raw) {
const str = raw == null ? '' : String(raw).trim();
if (!str) return [];
if (isOssIdList(str)) return [];
return str
.split(',')
.map(s => String(s).trim())
.filter(Boolean)
.filter(u => /^https?:\/\//i.test(u) || u.startsWith('/') || u.startsWith('data:'));
}
function imageUrls(row) {
const key = row?.productId != null ? String(row.productId) : '';
const cached = key ? imagePreviewMap.value[key] : null;
if (Array.isArray(cached)) return cached;
return normalizeImageUrlList(row?.productImages);
}
function prefetchImages(list) {
const rows = Array.isArray(list) ? list : [];
const targets = rows
.map(r => ({ productId: r?.productId, raw: String(r?.productImages || '').trim() }))
.filter(x => x.productId != null && x.raw && isOssIdList(x.raw));
if (!targets.length) return;
const idSet = new Set();
for (const it of targets) {
const ids = it.raw.split(',').map(s => String(s).trim()).filter(Boolean);
for (const id of ids) idSet.add(id);
}
const all = Array.from(idSet);
if (!all.length) return;
listByIds(all.join(',')).then(res => {
const list = res?.data || [];
const map = {};
for (const oss of list) {
if (oss?.ossId != null && oss?.url) {
map[String(oss.ossId)] = oss.url;
}
}
const next = { ...imagePreviewMap.value };
for (const it of targets) {
const urls = it.raw
.split(',')
.map(s => String(s).trim())
.filter(Boolean)
.map(id => map[String(id)])
.filter(Boolean);
next[String(it.productId)] = urls;
}
imagePreviewMap.value = next;
}).catch(() => {});
}
function pdfCount(row) {
const raw = String(row?.productPdfs || '').trim();
if (!raw) return 0;
@@ -1092,6 +1168,18 @@ getList();
gap: 6px;
}
.image-error {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background: #f5f7fa;
color: #c0c4cc;
border-radius: 2px;
font-size: 12px;
}
:deep(.el-table .el-table__cell) {
padding-top: 8px;
padding-bottom: 8px;

View File

@@ -74,13 +74,13 @@
{{ formatDecimal(scope.row.receivedQty) }}
</template>
</el-table-column>
<el-table-column label="收回状态" align="center" width="110">
<!-- <el-table-column label="收回状态" align="center" width="110">
<template #default="scope">
<el-tag :type="receiveStatusTagType(scope.row)" effect="dark">
{{ receiveStatusText(scope.row) }}
</el-tag>
</template>
</el-table-column>
</el-table-column> -->
<el-table-column label="委外时间" align="center" prop="outTime" width="180">
<template #default="scope">
<span>{{ formatterTime(scope.row.outTime) }}</span>
@@ -90,7 +90,7 @@
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120">
<template #default="scope">
<el-button link type="primary" icon="Check" :disabled="isFullyReceived(scope.row)" @click="openReceive(scope.row)">收回</el-button>
<!-- <el-button link type="primary" icon="Check" :disabled="isFullyReceived(scope.row)" @click="openReceive(scope.row)">收回</el-button> -->
<el-button link type="primary" icon="Delete" :disabled="hasReceived(scope.row)" @click="handleDelete(scope.row)">撤回</el-button>
</template>
</el-table-column>
@@ -149,7 +149,7 @@
</template>
</el-dialog>
<el-dialog title="收回" v-model="receiveOpen" width="460px" append-to-body>
<!-- <el-dialog title="收回" v-model="receiveOpen" width="460px" append-to-body>
<el-form ref="receiveRef" :model="receiveForm" label-width="90px">
<el-form-item label="收回数量">
<el-input-number v-model="receiveForm.receiveQty" style="width: 100%" :controls="false" :min="0" :precision="4" />
@@ -167,12 +167,12 @@
<el-button @click="receiveOpen = false"> </el-button>
</div>
</template>
</el-dialog>
</el-dialog> -->
</div>
</template>
<script setup name="ProductOutsourceOrder">
import { listProductOutsourceOrder, addProductOutsourceOrder, delProductOutsourceOrder, receiveProductOutsourceOrder } from "@/api/mat/productOutsourceOrder";
import { listProductOutsourceOrder, addProductOutsourceOrder, delProductOutsourceOrder } from "@/api/mat/productOutsourceOrder";
import { listProductBase } from "@/api/mat/product";
import useUserStore from '@/store/modules/user'
import { formatDecimal } from '@/utils/gear'
@@ -194,9 +194,9 @@ const title = ref("");
const productOptions = ref([]);
const productLoading = ref(false);
const receiveOpen = ref(false);
const receiveLoading = ref(false);
const receiveForm = ref({ orderId: null, receiveQty: null, receiveTime: null, receiveBy: null });
// const receiveOpen = ref(false);
// const receiveLoading = ref(false);
// const receiveForm = ref({ orderId: null, receiveQty: null, receiveTime: null, receiveBy: null });
const formatterTime = (time) => proxy.parseTime(time, '{y}-{m}-{d}')
const productTypeLabel = (val) => (val === 'semi' ? '半成品' : '成品');
@@ -204,20 +204,24 @@ const productTypeTagType = (val) => (val === 'semi' ? 'warning' : 'success');
const rowQty = (v) => (v === null || v === undefined ? 0 : Number(v) || 0);
const hasReceived = (row) => rowQty(row?.receivedQty) > 0;
const isFullyReceived = (row) => rowQty(row?.receivedQty) >= rowQty(row?.quantity);
const receiveStatusText = (row) => {
const totalQty = rowQty(row?.quantity);
const receivedQty = rowQty(row?.receivedQty);
if (receivedQty <= 0) return '未收回';
if (receivedQty >= totalQty) return '已收回';
return '部分收回';
};
const receiveStatusTagType = (row) => {
const t = receiveStatusText(row);
if (t === '已收回') return 'success';
if (t === '部分收回') return 'warning';
return 'info';
const isFullyReceived = (row) => {
const total = rowQty(row?.quantity);
if (total <= 0) return false;
return rowQty(row?.receivedQty) >= total;
};
// const receiveStatusText = (row) => {
// const totalQty = rowQty(row?.quantity);
// const receivedQty = rowQty(row?.receivedQty);
// if (receivedQty <= 0) return '未收回';
// if (receivedQty >= totalQty) return '已收回';
// return '部分收回';
// };
// const receiveStatusTagType = (row) => {
// const t = receiveStatusText(row);
// if (t === '已收回') return 'success';
// if (t === '部分收回') return 'warning';
// return 'info';
// };
const productOptionLabel = (p) => {
const name = p?.productName || '-';
@@ -243,7 +247,15 @@ const { queryParams, form, rules } = toRefs(data);
function getList() {
loading.value = true;
listProductOutsourceOrder(queryParams.value).then(res => {
list.value = res.rows;
const rows = (res && res.rows) ? res.rows : [];
const filtered = rows.filter(r => !isFullyReceived(r));
if (filtered.length === 0 && queryParams.value.pageNum > 1) {
queryParams.value.pageNum = queryParams.value.pageNum - 1;
loading.value = false;
getList();
return;
}
list.value = filtered;
total.value = res.total;
loading.value = false;
});
@@ -325,36 +337,36 @@ function handleDelete(row) {
});
}
function openReceive(row) {
const outTime = proxy.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
const totalQty = rowQty(row?.quantity);
const receivedQty = rowQty(row?.receivedQty);
const remaining = totalQty - receivedQty;
receiveForm.value = {
orderId: row?.orderId,
receiveQty: remaining > 0 ? remaining : 0,
receiveTime: outTime,
receiveBy: nickName
};
receiveOpen.value = true;
}
// function openReceive(row) {
// const outTime = proxy.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
// const totalQty = rowQty(row?.quantity);
// const receivedQty = rowQty(row?.receivedQty);
// const remaining = totalQty - receivedQty;
// receiveForm.value = {
// orderId: row?.orderId,
// receiveQty: remaining > 0 ? remaining : 0,
// receiveTime: outTime,
// receiveBy: nickName
// };
// receiveOpen.value = true;
// }
function submitReceive() {
if (receiveLoading.value) return;
const orderId = receiveForm.value.orderId;
receiveLoading.value = true;
receiveProductOutsourceOrder(orderId, {
receiveQty: receiveForm.value.receiveQty,
receiveTime: receiveForm.value.receiveTime,
receiveBy: receiveForm.value.receiveBy
}).then(() => {
proxy.$modal.msgSuccess("收回成功");
receiveOpen.value = false;
getList();
}).finally(() => {
receiveLoading.value = false;
});
}
// function submitReceive() {
// if (receiveLoading.value) return;
// const orderId = receiveForm.value.orderId;
// receiveLoading.value = true;
// receiveProductOutsourceOrder(orderId, {
// receiveQty: receiveForm.value.receiveQty,
// receiveTime: receiveForm.value.receiveTime,
// receiveBy: receiveForm.value.receiveBy
// }).then(() => {
// proxy.$modal.msgSuccess("收回成功");
// receiveOpen.value = false;
// getList();
// }).finally(() => {
// receiveLoading.value = false;
// });
// }
function handleExport() {
proxy.download('mat/productOutsourceOrder/export', {

View File

@@ -4,6 +4,12 @@
<el-form-item label="配料名称" prop="materialName">
<el-input v-model="queryParams.materialName" placeholder="请输入配料名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="规格" prop="spec">
<el-input v-model="queryParams.spec" placeholder="请输入规格" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="型号" prop="model">
<el-input v-model="queryParams.model" placeholder="请输入型号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="厂家" prop="factory">
<el-input v-model="queryParams.factory" placeholder="请输入厂家" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -44,6 +50,8 @@
<el-table-column type="selection" width="55" align="center" />
<!-- <el-table-column label="配料ID 主键" align="center" prop="materialId" v-if="true" /> -->
<el-table-column label="配料名称" align="center" prop="materialName" />
<el-table-column label="规格" align="center" prop="spec" min-width="140" show-overflow-tooltip />
<el-table-column label="型号" align="center" prop="model" min-width="140" show-overflow-tooltip />
<el-table-column label="物料类型" align="center" prop="materialType">
<template #default="scope">
{{ scope.row.materialType === 1 ? '辅料' : '主材' }}
@@ -79,6 +87,12 @@
<el-form-item label="配料名称" prop="materialName">
<el-input v-model="form.materialName" placeholder="请输入配料名称" />
</el-form-item>
<el-form-item label="规格" prop="spec">
<el-input v-model="form.spec" placeholder="请输入规格" />
</el-form-item>
<el-form-item label="型号" prop="model">
<el-input v-model="form.model" placeholder="请输入型号" />
</el-form-item>
<el-form-item label="物料类型" prop="materialType">
<el-select v-model="form.materialType" placeholder="请选择物料类型" disabled>
<el-option label="主材" value="2" />

View File

@@ -34,7 +34,7 @@
<span class="order-left-card__value">{{ item.customerName || "—" }}</span>
</div>
<div class="order-left-card__header-right">
<dict-tag class="order-left-card__status" :options="order_status" :value="item.orderStatus" />
<div class="order-left-card__status-spacer" />
</div>
</div>
@@ -86,13 +86,18 @@
<div class="top-attachment-title">
<span>订单附件</span>
<div class="top-attachment-actions">
<!-- 预览按钮打开弹窗预览避免在Tab上侧区域占用过多空间 -->
<el-button plain size="small" :disabled="!form.contractExcelOssIds" @click="openAttachmentPreview">
预览
<el-button
v-if="form.contractExcelOssIds"
plain
size="small"
style="margin-right: 8px;"
@click="clearAttachment"
>
删除附件
</el-button>
<FileUpload
v-model="form.contractExcelOssIds"
:limit="5"
:limit="1"
:file-size="20"
:file-type="['xls', 'xlsx', 'pdf']"
:is-show-tip="false"
@@ -102,50 +107,9 @@
</div>
<div class="top-attachment-body">
<!-- 附件概览只展示数量与文件名不在这里做预览 -->
<div class="top-attachment-summary">
<div class="top-attachment-summary__left">
<span class="top-attachment-summary__label">数量</span>
<span class="top-attachment-summary__value">{{ attachmentFiles.length }}</span>
</div>
<div class="top-attachment-summary__right">
<el-empty v-if="!attachmentFiles.length" description="暂无附件" />
<el-scrollbar v-else max-height="120px">
<div
v-for="(item, index) in attachmentFiles"
:key="item.ossId || item.url || index"
class="top-attachment-file"
>
{{ item.name }}
</div>
</el-scrollbar>
</div>
</div>
</div>
</div>
<!-- 订单附件预览弹窗左侧列表 + 右侧预览对齐产品说明书预览的形式 -->
<el-dialog title="订单附件预览" v-model="attachmentPreviewOpen" width="1200px" append-to-body>
<div class="attachment-preview-wrap" v-loading="attachmentPreviewLoading">
<div class="attachment-list">
<div class="attachment-list-title">附件列表{{ attachmentFiles.length }}</div>
<el-empty v-if="!attachmentFiles.length" description="暂无附件" />
<el-scrollbar v-else max-height="520px">
<div
v-for="(item, index) in attachmentFiles"
:key="item.ossId || item.url || index"
class="attachment-item"
:class="{ active: item.url === currentAttachmentUrl }"
@click="selectAttachment(item)"
>
{{ item.name }}
</div>
</el-scrollbar>
</div>
<div class="attachment-viewer">
<el-empty v-if="!currentAttachmentUrl" description="请选择附件" />
<div class="attachment-viewer top-attachment-single-viewer">
<el-empty v-if="!currentAttachmentUrl" description="暂无附件" />
<iframe v-else-if="canIframePreview" :src="iframePreviewUrl" class="attachment-iframe" />
<!-- Excel 预览不依赖第三方在线预览直接在前端解析展示不保证样式100%一致但可看数据 -->
<div v-else-if="isExcelPreview" class="excel-preview-wrap" v-loading="excelPreviewLoading">
<component
v-if="vueOfficeExcelComp && officeExcelUrl"
@@ -163,7 +127,7 @@
<div class="excel-table-tip" v-if="excelTruncated">
预览已截断仅展示前 {{ excelMaxRows }} {{ excelMaxCols }}
</div>
<el-scrollbar max-height="520px">
<el-scrollbar max-height="420px">
<table class="excel-preview-table">
<tbody>
<tr v-for="(row, rIdx) in excelPreviewRows" :key="rIdx">
@@ -183,12 +147,7 @@
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="attachmentPreviewOpen = false">关闭</el-button>
</div>
</template>
</el-dialog>
</div>
<!-- 发货单据新增弹窗写入新表 gear_shipping_order与出入库单据无关 -->
<el-dialog title="新增发货单据" v-model="shippingAddOpen" width="900px" append-to-body>
@@ -304,12 +263,86 @@
<el-table :data="orderDetailList" size="small" border>
<el-table-column label="产品编号" prop="productCode" min-width="120" />
<el-table-column label="产品名称" prop="productName" min-width="150" show-overflow-tooltip />
<el-table-column label="产品规格" prop="spec" min-width="120" show-overflow-tooltip />
<el-table-column label="产品型号" prop="model" min-width="120" show-overflow-tooltip />
<el-table-column label="预计发出数量" prop="quantity" width="120" align="center" />
<el-table-column label="单位" prop="unit" width="80" align="center" />
<el-table-column label="产品单价" prop="unitPrice" width="100" align="center" />
<el-table-column label="备注" prop="remark" min-width="150" show-overflow-tooltip />
<el-table-column label="产品规格" min-width="150">
<template #default="scope">
<el-input
v-model="scope.row._editSpec"
size="small"
placeholder="规格"
:disabled="!orderDetailEditable || scope.row._saving"
/>
</template>
</el-table-column>
<el-table-column label="产品型号" min-width="140">
<template #default="scope">
<el-input
v-model="scope.row._editModel"
size="small"
placeholder="型号"
:disabled="!orderDetailEditable || scope.row._saving"
/>
</template>
</el-table-column>
<el-table-column label="预计发出数量" width="140" align="center">
<template #default="scope">
<el-input-number
v-model="scope.row._editQuantity"
size="small"
:controls="false"
:min="0"
:precision="0"
style="width: 120px;"
:disabled="!orderDetailEditable || scope.row._saving"
/>
</template>
</el-table-column>
<el-table-column label="单位" width="110" align="center">
<template #default="scope">
<el-input
v-model="scope.row._editUnit"
size="small"
placeholder="单位"
:disabled="!orderDetailEditable || scope.row._saving"
/>
</template>
</el-table-column>
<el-table-column label="产品单价" width="140" align="center">
<template #default="scope">
<el-input-number
v-model="scope.row._editUnitPrice"
size="small"
:controls="false"
:min="0"
:precision="4"
style="width: 120px;"
:disabled="!orderDetailEditable || scope.row._saving"
/>
</template>
</el-table-column>
<el-table-column label="备注" min-width="160">
<template #default="scope">
<el-input
v-model="scope.row._editRemark"
size="small"
placeholder="备注"
:disabled="!orderDetailEditable || scope.row._saving"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template #default="scope">
<el-button
link
type="primary"
size="small"
:loading="scope.row._saving"
:disabled="!orderDetailEditable || !isOrderDetailRowChanged(scope.row)"
@click="saveOrderDetailRow(scope.row)"
>
保存
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
@@ -394,6 +427,7 @@
</div>
</el-tab-pane>
<!--
<el-tab-pane label="生产成果" name="production">
<div class="mt-4" v-loading="productionLoading">
<div class="production-toolbar">
@@ -458,6 +492,7 @@
</el-table>
</div>
</el-tab-pane>
-->
<el-tab-pane label="发货" name="shippingAlloc">
<div class="mt-4">
@@ -650,7 +685,7 @@ import ReturnExchange from './return.vue';
import Receive from './receive.vue';
import { listSalesman } from "@/api/oms/salesman";
import { listShippingOrder, addShippingOrder, delShippingOrder } from "@/api/oms/shippingOrder";
import { listOrderDetail, addOrderDetail } from "@/api/oms/orderDetail";
import { listOrderDetail, addOrderDetail, updateOrderDetail } from "@/api/oms/orderDetail";
import { listProductBase } from "@/api/mat/product";
import { sumStockQuantityByItemIds } from "@/api/wms/stock";
import { listReceivable } from "@/api/finance/receivable";
@@ -709,6 +744,11 @@ export default {
const list = Array.isArray(this.productionList) ? this.productionList : [];
if (!this.productionOnlyProduct) return list;
return list.filter(r => String(r && r.productType || "") === "product");
},
orderDetailEditable() {
const s = this.form && this.form.orderStatus != null ? Number(this.form.orderStatus) : NaN;
if (Number.isNaN(s)) return true;
return s !== EOrderStatus.CANCEL;
}
},
watch: {
@@ -748,8 +788,6 @@ export default {
receivedAmount: 0,
unreceivedAmount: 0
},
// 附件预览弹窗:点击“预览”按钮后打开
attachmentPreviewOpen: false,
attachmentPreviewLoading: false,
attachmentFiles: [],
currentAttachmentOssId: null,
@@ -788,6 +826,8 @@ export default {
quantity: 1,
unit: "",
unitPrice: "",
spec: "",
model: "",
remark: ""
},
productionLoading: false,
@@ -985,9 +1025,9 @@ export default {
if (this.activeTab === "logs") {
this.loadOperLogs();
}
if (this.activeTab === "production") {
this.loadProduction(item.orderId);
}
// if (this.activeTab === "production") {
// this.loadProduction(item.orderId);
// }
},
/** 加载订单详情 */
@@ -1018,9 +1058,9 @@ export default {
if (this.activeTab === "logs") {
this.loadOperLogs();
}
if (this.activeTab === "production") {
this.loadProduction(orderId);
}
// if (this.activeTab === "production") {
// this.loadProduction(orderId);
// }
}).catch(() => {
this.$modal.msgError("加载订单详情失败");
}).finally(() => {
@@ -1042,12 +1082,18 @@ export default {
});
},
/** 点击“预览”按钮:打开附件预览弹窗 */
openAttachmentPreview() {
this.attachmentPreviewOpen = true;
if (!this.attachmentFiles.length) {
clearAttachment() {
if (!this.form || !this.form.orderId) return;
updateOrder({
orderId: this.form.orderId,
contractExcelOssIds: ""
}).then(() => {
this.form.contractExcelOssIds = "";
this.$modal.msgSuccess("附件已删除");
this.loadAttachmentFiles();
}
}).catch(() => {
this.$modal.msgError("附件删除失败");
});
},
/** 加载附件列表ossId串 -> 文件列表) */
@@ -1060,18 +1106,28 @@ export default {
this.currentAttachmentExt = "";
return;
}
const firstId = raw.split(",").map(s => String(s).trim()).filter(Boolean)[0] || "";
if (firstId && firstId !== raw) {
this.form.contractExcelOssIds = firstId;
}
this.attachmentPreviewLoading = true;
listByIds(raw).then(res => {
listByIds(firstId || raw).then(res => {
const list = (res && res.data) ? res.data : [];
this.attachmentFiles = list.map((oss, idx) => {
const name = oss.originalName || oss.fileName || String(oss.ossId);
const url = oss.url;
return { name, url, ossId: oss.ossId, uid: oss.ossId || (Date.now() + idx) };
});
// 如果当前没有选中附件,或者选中的附件已经不在列表里(例如切换订单/删除/重新上传),默认选中一条
// 如果当前没有选中附件,或者选中的附件已经不在列表里(例如切换订单/删除/重新上传),默认选中一条(优先 Excel
const hasCurrent = this.attachmentFiles.some(f => f.url === this.currentAttachmentUrl);
if (this.attachmentFiles.length > 0 && (!this.currentAttachmentUrl || !hasCurrent)) {
this.selectAttachment(this.attachmentFiles[0]);
const prefer = this.attachmentFiles.find(f => {
const name = String(f && f.name ? f.name : "");
const ext = name.split(".").pop();
const e = ext ? String(ext).toLowerCase() : "";
return e === "xls" || e === "xlsx";
});
this.selectAttachment(prefer || this.attachmentFiles[0]);
}
}).catch(() => {
this.attachmentFiles = [];
@@ -1282,9 +1338,9 @@ export default {
if (name === "logs") {
this.loadOperLogs();
}
if (name === "production") {
this.loadProduction(this.selectedOrderId);
}
// if (name === "production") {
// this.loadProduction(this.selectedOrderId);
// }
},
loadProduction(orderId) {
@@ -1468,7 +1524,22 @@ export default {
return listOrderDetail({ orderId: orderId, pageNum: 1, pageSize: 9999 }).then(res => {
const rows = res.rows || [];
// 订单管理页的“发货/订单明细”口径只展示成品product避免半成品/原料进入发货视图
this.orderDetailList = rows.filter(r => String(r.productType || "").toLowerCase() === "product");
this.orderDetailList = rows
.filter(r => String(r.productType || "").toLowerCase() === "product")
.map(r => {
const q = r && r.quantity != null ? Number(r.quantity) : 0;
const unitPrice = r && r.taxPrice != null ? Number(r.taxPrice) : (r && r.unitPrice != null ? Number(r.unitPrice) : 0);
return {
...r,
_editSpec: r && r.spec != null ? String(r.spec) : "",
_editModel: r && r.model != null ? String(r.model) : "",
_editQuantity: Number.isFinite(q) ? q : 0,
_editUnit: r && r.unit != null ? String(r.unit) : "",
_editUnitPrice: Number.isFinite(unitPrice) ? unitPrice : 0,
_editRemark: r && r.remark != null ? String(r.remark) : "",
_saving: false
};
});
return this.orderDetailList;
}).catch(() => {
this.orderDetailList = [];
@@ -1608,6 +1679,8 @@ export default {
quantity: 1,
unit: "",
unitPrice: "",
spec: "",
model: "",
remark: ""
};
this.orderDetailAddOpen = true;
@@ -1630,6 +1703,8 @@ export default {
if (!hit) return;
this.orderDetailAddForm.unit = hit.unit || this.orderDetailAddForm.unit;
this.orderDetailAddForm.unitPrice = hit.unitPrice != null ? String(hit.unitPrice) : this.orderDetailAddForm.unitPrice;
this.orderDetailAddForm.spec = hit.spec != null ? String(hit.spec) : this.orderDetailAddForm.spec;
this.orderDetailAddForm.model = hit.model != null ? String(hit.model) : this.orderDetailAddForm.model;
},
/** 订单明细:提交新增(写入 gear_order_detail */
@@ -1644,12 +1719,18 @@ export default {
return;
}
this.orderDetailAddLoading = true;
const priceNum = this.orderDetailAddForm.unitPrice != null && String(this.orderDetailAddForm.unitPrice).trim() !== ""
? Number(this.orderDetailAddForm.unitPrice)
: null;
addOrderDetail({
orderId: this.selectedOrderId,
productId: this.orderDetailAddForm.productId,
quantity: this.orderDetailAddForm.quantity,
unit: this.orderDetailAddForm.unit,
remark: this.orderDetailAddForm.remark
remark: this.orderDetailAddForm.remark,
spec: this.orderDetailAddForm.spec,
model: this.orderDetailAddForm.model,
taxPrice: priceNum != null && Number.isFinite(priceNum) ? priceNum : undefined
}).then(() => {
this.$modal.msgSuccess("新增成功");
this.orderDetailAddOpen = false;
@@ -1659,6 +1740,46 @@ export default {
});
},
isOrderDetailRowChanged(row) {
if (!row) return false;
const q0 = row.quantity != null ? Number(row.quantity) : 0;
const q1 = row._editQuantity != null ? Number(row._editQuantity) : 0;
const u0 = row.unit != null ? String(row.unit) : "";
const u1 = row._editUnit != null ? String(row._editUnit) : "";
const r0 = row.remark != null ? String(row.remark) : "";
const r1 = row._editRemark != null ? String(row._editRemark) : "";
const s0 = row.spec != null ? String(row.spec) : "";
const s1 = row._editSpec != null ? String(row._editSpec) : "";
const m0 = row.model != null ? String(row.model) : "";
const m1 = row._editModel != null ? String(row._editModel) : "";
const p0 = row.taxPrice != null ? Number(row.taxPrice) : (row.unitPrice != null ? Number(row.unitPrice) : 0);
const p1 = row._editUnitPrice != null ? Number(row._editUnitPrice) : 0;
const priceChanged = (Number.isFinite(p0) ? p0 : 0) !== (Number.isFinite(p1) ? p1 : 0);
return q0 !== q1 || u0 !== u1 || r0 !== r1 || s0 !== s1 || m0 !== m1 || priceChanged;
},
saveOrderDetailRow(row) {
if (!row || !row.detailId) return;
if (!this.isOrderDetailRowChanged(row)) return;
row._saving = true;
const payload = {
detailId: row.detailId,
orderId: row.orderId,
quantity: row._editQuantity != null ? Number(row._editQuantity) : 0,
unit: row._editUnit != null ? String(row._editUnit) : "",
remark: row._editRemark != null ? String(row._editRemark) : "",
spec: row._editSpec != null ? String(row._editSpec) : "",
model: row._editModel != null ? String(row._editModel) : "",
taxPrice: row._editUnitPrice != null ? Number(row._editUnitPrice) : 0
};
updateOrderDetail(payload).then(() => {
this.$modal.msgSuccess("已保存");
this.loadShippingAllocData(this.selectedOrderId);
}).finally(() => {
row._saving = false;
});
},
// 发货单据:打开新增弹窗(默认带入订单信息,由后端生成发货单号)
openShippingAdd() {
if (!this.selectedOrderId) return;
@@ -1919,6 +2040,10 @@ export default {
flex: none;
}
.order-left-card__status-spacer {
width: 64px;
}
.order-left-card__status :deep(.el-tag) {
border-radius: 4px;
}
@@ -2090,8 +2215,7 @@ export default {
}
.top-attachment-actions :deep(.upload-file-list) {
/* 显示上传后的文件列表(用户能直观看到“已上传了哪些文件”) */
display: block;
display: none;
}
.top-attachment-actions :deep(.upload-file-list .el-upload-list__item) {
@@ -2102,6 +2226,10 @@ export default {
padding: 10px;
}
.top-attachment-single-viewer {
height: 420px;
}
.top-attachment-summary {
display: flex;
gap: 12px;

View File

@@ -65,9 +65,12 @@
<el-tabs v-model="activeTab" class="right-tabs" @tab-change="handleTabChange">
<el-tab-pane label="跟进客户" name="followCustomer" />
<el-tab-pane label="跟进合同" name="followContract" />
<el-tab-pane label="历史订单" name="historyOrders" />
<el-tab-pane label="订单异议" name="dispute" />
<el-tab-pane label="财务状态" name="finance" />
<el-tab-pane label="发货单据" name="shippingDocs" />
<el-tab-pane label="生产成果" name="production" />
<el-tab-pane label="计划发货" name="planShipping" />
<el-tab-pane label="销售信息编辑" name="salesmanInfo" />
</el-tabs>
<div class="right-body">
@@ -120,6 +123,115 @@
/>
</div>
<div v-show="activeTab === 'historyOrders'">
<div class="orders-section">
<div class="finance-title-row">
<div class="finance-title">历史订单</div>
<div class="history-actions">
<el-input
v-model="historyQuery.orderCode"
size="small"
clearable
placeholder="订单编号"
style="width: 180px"
@keyup.enter="loadHistoryOrders"
/>
<el-button size="small" plain icon="Refresh" :loading="historyLoading" @click="loadHistoryOrders">刷新</el-button>
</div>
</div>
<el-table v-loading="historyLoading" :data="historyOrderList" border stripe :header-cell-style="{ background: '#f5f7fa' }">
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
<el-table-column label="客户" prop="customerName" min-width="200" show-overflow-tooltip />
<el-table-column label="状态" width="120" align="center">
<template #default="scope">
<el-tag :type="orderStatusTagType(scope.row.orderStatus)" size="small">
{{ orderStatusLabel(scope.row.orderStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createTime" width="180" />
</el-table>
</div>
</div>
<div v-show="activeTab === 'dispute'">
<div class="orders-section">
<div class="finance-title-row">
<div class="finance-title">订单异议</div>
<el-button size="small" plain icon="Refresh" :loading="disputeLoading" @click="loadDisputeOrders">刷新</el-button>
</div>
<el-table v-loading="disputeLoading" :data="disputeOrderList" border stripe :header-cell-style="{ background: '#f5f7fa' }">
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
<el-table-column label="客户" prop="customerName" min-width="200" show-overflow-tooltip />
<el-table-column label="状态" width="120" align="center">
<template #default="scope">
<el-tag :type="orderStatusTagType(scope.row.orderStatus)" size="small">
{{ orderStatusLabel(scope.row.orderStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createTime" width="180" />
<el-table-column label="操作" width="120" align="center">
<template #default="scope">
<el-button link type="primary" icon="View" @click="openDispute(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="订单异议" v-model="disputeOpen" width="1200px" append-to-body>
<el-empty v-if="!disputeOrderId" description="请选择订单" />
<ReturnExchange v-else :orderId="disputeOrderId" />
<template #footer>
<div class="dialog-footer">
<el-button @click="disputeOpen = false">关闭</el-button>
</div>
</template>
</el-dialog>
</div>
</div>
<div v-show="activeTab === 'finance'">
<div class="orders-section">
<div class="finance-title-row">
<div class="finance-title">财务状态</div>
<el-button size="small" plain icon="Refresh" :loading="financeLoading" @click="loadFinance">刷新</el-button>
</div>
<div class="finance-strip" v-loading="financeLoading">
<div class="finance-item">
<div class="finance-item__label">应收总金额</div>
<div class="finance-item__value">{{ formatMoney(financeSummary.receivableAmount) }}</div>
</div>
<div class="finance-item">
<div class="finance-item__label">已收款金额</div>
<div class="finance-item__value">{{ formatMoney(financeSummary.receivedAmount) }}</div>
</div>
<div class="finance-item">
<div class="finance-item__label">未收款金额</div>
<div class="finance-item__value">{{ formatMoney(financeSummary.unreceivedAmount) }}</div>
</div>
</div>
<div class="finance-subtitle">收款明细</div>
<el-table v-loading="financeLoading" :data="financeList" border stripe :header-cell-style="{ background: '#f5f7fa' }">
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
<el-table-column label="客户" prop="customerName" min-width="180" show-overflow-tooltip />
<el-table-column label="到期日" prop="dueDate" width="180" />
<el-table-column label="应收" prop="amount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.amount) }}</template>
</el-table-column>
<el-table-column label="已收" prop="paidAmount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.paidAmount) }}</template>
</el-table-column>
<el-table-column label="未收" prop="balanceAmount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.balanceAmount) }}</template>
</el-table-column>
<el-table-column label="状态" prop="status" width="120" />
<el-table-column label="备注" prop="remark" min-width="180" show-overflow-tooltip />
</el-table>
</div>
</div>
<div v-show="activeTab === 'shippingDocs'">
<el-table v-loading="shippingLoading" :data="shippingList">
<el-table-column label="发货单号" prop="shippingNo" min-width="180" />
@@ -132,29 +244,6 @@
</el-table>
</div>
<div v-show="activeTab === 'production'">
<div class="production-wrap">
<div class="production-summary">
<div class="production-summary__item">订单数{{ productionSummary.orderCount }}</div>
<div class="production-summary__item">计划总量{{ formatQty(productionSummary.planQty) }}</div>
<div class="production-summary__item">已完成总量{{ formatQty(productionSummary.finishedQty) }}</div>
<div class="production-summary__item">完成率{{ productionSummary.rateText }}</div>
</div>
<el-table v-loading="productionLoading" :data="productionOrderSummaryList" size="small" border>
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
<el-table-column label="客户" prop="customerName" min-width="180" show-overflow-tooltip />
<el-table-column label="计划" prop="planQty" width="120" align="center" />
<el-table-column label="完成" prop="finishedQty" width="120" align="center" />
<el-table-column label="完成率" width="120" align="center">
<template #default="scope">
{{ formatRate(scope.row.finishedQty, scope.row.planQty) }}
</template>
</el-table-column>
<el-table-column label="最近更新" prop="lastUpdateTime" min-width="160" />
</el-table>
</div>
</div>
<div v-show="activeTab === 'planShipping'">
<el-card shadow="never">
<template #header>
@@ -212,6 +301,30 @@
</el-table>
</el-card>
</div>
<div v-show="activeTab === 'salesmanInfo'">
<el-form ref="salesmanInfoRef" :model="salesmanInfoForm" label-width="90px" class="salesman-info-form">
<el-form-item label="姓名">
<el-input v-model="salesmanInfoForm.name" placeholder="请输入销售员姓名" />
</el-form-item>
<el-form-item label="手机">
<el-input v-model="salesmanInfoForm.mobile" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="salesmanInfoForm.status">
<el-radio :value="0">正常</el-radio>
<el-radio :value="1">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="salesmanInfoForm.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="salesmanInfoSaving" @click="saveSalesmanInfo">保存</el-button>
<el-button @click="resetSalesmanInfo">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
</div>
</el-card>
@@ -252,16 +365,19 @@ import { listSalesman, addSalesman, updateSalesman, delSalesman, listSalesmanCus
import { listOrder } from "@/api/oms/order";
import { listShippingOrder } from "@/api/oms/shippingOrder";
import { listOrderDetail } from "@/api/oms/orderDetail";
import { listOrderProduction } from "@/api/oms/orderProduction";
import { listReceivable } from "@/api/finance/receivable";
import ReturnExchange from "@/views/oms/order/panels/return.vue";
export default {
name: "Salesman",
components: { ReturnExchange },
data() {
return {
leftLoading: false,
salesmanList: [],
selectedSalesmanId: undefined,
selectedSalesmanName: "",
selectedSalesman: {},
activeTab: "followCustomer",
// 左侧查询条件(用于筛选销售员列表)
@@ -290,18 +406,38 @@ export default {
pageSize: 20
},
historyLoading: false,
historyOrderList: [],
historyQuery: {
orderCode: ""
},
// 发货单据
shippingLoading: false,
shippingList: [],
// 生产成果:按订单汇总(只读)
productionLoading: false,
productionOrderSummaryList: [],
financeLoading: false,
financeList: [],
financeSummary: {
receivableAmount: 0,
receivedAmount: 0,
unreceivedAmount: 0
},
financeOrderCodeMap: {},
disputeLoading: false,
disputeOrderList: [],
disputeOpen: false,
disputeOrderId: null,
// 计划发货:产品明细(按销售员全部订单汇总)
planDetailLoading: false,
planOrderDetailList: [],
salesmanInfoForm: {},
salesmanInfoFormOrigin: {},
salesmanInfoSaving: false,
// 新增/修改弹窗
dialogOpen: false,
dialogTitle: "",
@@ -326,14 +462,6 @@ export default {
shippedOrders
};
},
productionSummary() {
const list = Array.isArray(this.productionOrderSummaryList) ? this.productionOrderSummaryList : [];
const orderCount = list.length;
const planQty = list.reduce((sum, r) => sum + Number(r && r.planQty != null ? r.planQty : 0), 0);
const finishedQty = list.reduce((sum, r) => sum + Number(r && r.finishedQty != null ? r.finishedQty : 0), 0);
const rateText = planQty > 0 ? `${((finishedQty / planQty) * 100).toFixed(1)}%` : "0%";
return { orderCount, planQty, finishedQty, rateText };
}
},
created() {
this.getSalesmanList();
@@ -373,18 +501,26 @@ export default {
if (!item || !item.salesmanId) return;
this.selectedSalesmanId = item.salesmanId;
this.selectedSalesmanName = item.name;
this.selectedSalesman = { ...item };
this.initSalesmanInfoForm(item);
this.customerQuery.pageNum = 1;
this.orderQuery.pageNum = 1;
this.loadCustomers();
if (this.activeTab === "followContract") {
this.loadOrders();
}
if (this.activeTab === "historyOrders") {
this.loadHistoryOrders();
}
if (this.activeTab === "dispute") {
this.loadDisputeOrders();
}
if (this.activeTab === "finance") {
this.loadFinance();
}
if (this.activeTab === "shippingDocs") {
this.loadShippingOrders();
}
if (this.activeTab === "production") {
this.loadProductionSummary();
}
if (this.activeTab === "planShipping") {
this.loadPlanShipping();
}
@@ -399,15 +535,24 @@ export default {
if (this.activeTab === "followContract") {
this.loadOrders();
}
if (this.activeTab === "historyOrders") {
this.loadHistoryOrders();
}
if (this.activeTab === "dispute") {
this.loadDisputeOrders();
}
if (this.activeTab === "finance") {
this.loadFinance();
}
if (this.activeTab === "shippingDocs") {
this.loadShippingOrders();
}
if (this.activeTab === "production") {
this.loadProductionSummary();
}
if (this.activeTab === "planShipping") {
this.loadPlanShipping();
}
if (this.activeTab === "salesmanInfo") {
this.initSalesmanInfoForm(this.selectedSalesman);
}
},
// 跟进客户:后端反查客户
@@ -441,6 +586,53 @@ export default {
});
},
loadHistoryOrders() {
if (!this.selectedSalesmanId) return;
this.historyLoading = true;
listOrder({
salesmanId: this.selectedSalesmanId,
orderCode: this.historyQuery.orderCode || undefined,
pageNum: 1,
pageSize: 9999
})
.then(res => {
const list = (res && res.rows) ? res.rows : [];
this.historyOrderList = list.filter(o => this.isHistoryOrder(o));
})
.finally(() => {
this.historyLoading = false;
});
},
isHistoryOrder(order) {
const s = order && order.orderStatus != null ? order.orderStatus : null;
const n = Number(s);
if (Number.isFinite(n)) return n === 2 || n === 3;
const t = String(s || "");
return t.includes("完成") || t.includes("取消");
},
orderStatusLabel(status) {
const n = Number(status);
if (Number.isFinite(n)) {
if (n === 0) return "预订单";
if (n === 1) return "进行中";
if (n === 2) return "已完成";
if (n === 3) return "已取消";
}
return String(status || "-");
},
orderStatusTagType(status) {
const n = Number(status);
if (Number.isFinite(n)) {
if (n === 2) return "success";
if (n === 3) return "info";
if (n === 1) return "warning";
}
return "info";
},
loadShippingOrders() {
if (!this.selectedSalesmanId) return;
this.shippingLoading = true;
@@ -453,40 +645,71 @@ export default {
});
},
async loadProductionSummary() {
loadDisputeOrders() {
if (!this.selectedSalesmanId) return;
this.productionLoading = true;
try {
const orderRes = await listOrder({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 });
const orders = (orderRes && orderRes.rows) ? orderRes.rows : [];
const promises = orders
.filter(o => o && o.orderId != null)
.map(async (o) => {
const prodRes = await listOrderProduction({ orderId: o.orderId, pageNum: 1, pageSize: 9999 });
const rows = (prodRes && prodRes.rows) ? prodRes.rows : [];
const planQty = rows.reduce((sum, r) => sum + Number(r && r.planQty != null ? r.planQty : 0), 0);
const finishedQty = rows.reduce((sum, r) => sum + Number(r && r.finishedQty != null ? r.finishedQty : 0), 0);
let lastUpdateTime = "";
rows.forEach(r => {
const t = r && r.updateTime ? String(r.updateTime) : "";
if (t && (!lastUpdateTime || new Date(t).getTime() > new Date(lastUpdateTime).getTime())) {
lastUpdateTime = t;
}
});
this.disputeLoading = true;
listOrder({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 })
.then(res => {
this.disputeOrderList = (res && res.rows) ? res.rows : [];
})
.finally(() => {
this.disputeLoading = false;
});
},
openDispute(row) {
if (!row || !row.orderId) return;
this.disputeOrderId = row.orderId;
this.disputeOpen = true;
},
formatMoney(v) {
const n = Number(v);
return Number.isFinite(n) ? n.toFixed(2) : "0.00";
},
loadFinance() {
if (!this.selectedSalesmanId) return;
this.financeLoading = true;
Promise.all([
listReceivable({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 }),
listOrder({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 })
])
.then(([recRes, orderRes]) => {
const receivables = (recRes && recRes.rows) ? recRes.rows : [];
const orders = (orderRes && orderRes.rows) ? orderRes.rows : [];
const codeMap = {};
orders.forEach(o => {
if (!o || o.orderId == null) return;
codeMap[o.orderId] = o.orderCode || String(o.orderId);
});
this.financeOrderCodeMap = codeMap;
const rows = receivables.map(r => {
const orderId = r && r.orderId != null ? r.orderId : null;
return {
orderId: o.orderId,
orderCode: o.orderCode,
customerName: o.customerName,
planQty,
finishedQty,
lastUpdateTime
...r,
orderCode: orderId != null ? (codeMap[orderId] || String(orderId)) : "-"
};
});
this.financeList = rows;
this.productionOrderSummaryList = await Promise.all(promises);
} finally {
this.productionLoading = false;
}
const receivableAmount = rows.reduce((sum, r) => sum + (Number(r && r.amount != null ? r.amount : 0) || 0), 0);
const receivedAmount = rows.reduce((sum, r) => sum + (Number(r && r.paidAmount != null ? r.paidAmount : 0) || 0), 0);
const unreceivedAmount = rows.reduce((sum, r) => {
const bal = r && r.balanceAmount != null ? Number(r.balanceAmount) : NaN;
if (Number.isFinite(bal)) return sum + bal;
const a = Number(r && r.amount != null ? r.amount : 0) || 0;
const p = Number(r && r.paidAmount != null ? r.paidAmount : 0) || 0;
return sum + Math.max(0, a - p);
}, 0);
this.financeSummary = { receivableAmount, receivedAmount, unreceivedAmount };
})
.finally(() => {
this.financeLoading = false;
});
},
loadPlanShipping() {
@@ -564,6 +787,42 @@ export default {
return `${((fa / fb) * 100).toFixed(1)}%`;
},
initSalesmanInfoForm(item) {
const row = item && item.salesmanId ? item : null;
if (!row) {
this.salesmanInfoForm = {};
this.salesmanInfoFormOrigin = {};
return;
}
const form = {
salesmanId: row.salesmanId,
name: row.name || "",
mobile: row.mobile || "",
status: row.status != null ? row.status : 0,
remark: row.remark || ""
};
this.salesmanInfoForm = { ...form };
this.salesmanInfoFormOrigin = { ...form };
},
resetSalesmanInfo() {
this.salesmanInfoForm = { ...this.salesmanInfoFormOrigin };
},
saveSalesmanInfo() {
if (!this.salesmanInfoForm || !this.salesmanInfoForm.salesmanId) return;
this.salesmanInfoSaving = true;
updateSalesman(this.salesmanInfoForm)
.then(() => {
this.$modal.msgSuccess("保存成功");
this.salesmanInfoFormOrigin = { ...this.salesmanInfoForm };
this.getSalesmanList();
})
.finally(() => {
this.salesmanInfoSaving = false;
});
},
// 新增
handleAdd() {
this.dialogTitle = "新增销售员";
@@ -758,4 +1017,63 @@ export default {
.production-summary__item {
font-size: 13px;
}
.orders-section {
padding-top: 6px;
}
.finance-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.history-actions {
display: flex;
align-items: center;
gap: 8px;
}
.salesman-info-form {
max-width: 680px;
}
.finance-title {
font-weight: 600;
color: #303133;
}
.finance-strip {
display: flex;
gap: 18px;
padding: 10px 12px;
margin-bottom: 10px;
background: #f5f7fa;
border: 1px solid var(--el-border-color-light);
border-radius: 6px;
}
.finance-item {
flex: 1;
min-width: 140px;
}
.finance-item__label {
font-size: 12px;
color: #909399;
margin-bottom: 4px;
}
.finance-item__value {
font-size: 16px;
font-weight: 700;
color: #303133;
}
.finance-subtitle {
font-weight: 600;
color: #303133;
margin: 10px 0;
}
</style>

View File

@@ -141,8 +141,13 @@
</el-descriptions>
<div class="shipping-detail-products" v-loading="detailProductsLoading">
<!-- 发货单绑定订单后展示该订单的订单明细仅成品用于发货与打印 -->
<div class="shipping-detail-products__title">订单明细成品</div>
<div class="shipping-detail-products__title-row">
<div class="shipping-detail-products__title">发货单据明细</div>
<div class="shipping-detail-products__ops">
<el-button size="small" type="primary" plain icon="Plus" @click="handleDetailAdd">新增产品</el-button>
<el-button size="small" plain icon="Refresh" @click="reloadCurrentDetail">刷新</el-button>
</div>
</div>
<el-table :data="detailProducts" size="small" border>
<el-table-column label="产品编号" prop="productCode" min-width="120" />
<el-table-column label="产品名称" prop="productName" min-width="160" show-overflow-tooltip />
@@ -151,6 +156,12 @@
<el-table-column label="数量" prop="quantity" width="100" align="center" />
<el-table-column label="单位" prop="unit" width="90" align="center" />
<el-table-column label="备注" prop="remark" min-width="160" show-overflow-tooltip />
<el-table-column label="操作" width="140" align="center">
<template #default="scope">
<el-button link type="primary" size="small" @click="handleDetailEdit(scope.row)">修改</el-button>
<el-button link type="danger" size="small" @click="handleDetailDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="shipping-detail-products__extra" v-if="detailProducts && detailProducts.length">
@@ -265,9 +276,15 @@
</el-form>
<el-table v-loading="orderSelectLoading" :data="orderSelectList" highlight-current-row @row-dblclick="confirmOrderBind">
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
<el-table-column label="订单ID" prop="orderId" min-width="160" />
<el-table-column label="客户ID" prop="customerId" min-width="120" />
<el-table-column label="状态" prop="orderStatus" width="100" />
<el-table-column label="客户" prop="customerName" min-width="200" show-overflow-tooltip />
<el-table-column label="销售员" prop="salesManager" min-width="120" show-overflow-tooltip />
<el-table-column label="订单状态" width="110" align="center">
<template #default="scope">
<el-tag :type="orderStatusTagType(scope.row.orderStatus)" effect="dark">
{{ orderStatusLabel(scope.row.orderStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createTime" min-width="160" />
<el-table-column label="操作" width="100" align="center">
<template #default="scope">
@@ -287,6 +304,50 @@
</template>
</el-dialog>
<el-dialog :title="detailEditTitle" v-model="detailEditOpen" width="520px" append-to-body>
<el-form ref="detailEditFormRef" :model="detailEditForm" :rules="detailEditRules" label-width="90px" v-loading="detailEditLoading">
<el-form-item label="产品" prop="productId">
<el-select
v-model="detailEditForm.productId"
placeholder="请选择产品"
clearable
filterable
remote
:remote-method="remoteSearchDetailProducts"
:loading="detailProductLoading"
style="width: 100%"
@change="handleDetailProductPicked"
>
<el-option
v-for="p in detailProductOptions"
:key="p.productId"
:label="detailProductOptionLabel(p)"
:value="p.productId"
/>
</el-select>
</el-form-item>
<el-form-item label="规格">
<el-input v-model="detailEditForm.spec" disabled />
</el-form-item>
<el-form-item label="型号">
<el-input v-model="detailEditForm.model" disabled />
</el-form-item>
<el-form-item label="数量" prop="quantity">
<el-input-number v-model="detailEditForm.quantity" style="width: 100%" :controls="false" :min="0" :precision="4" />
</el-form-item>
<el-form-item label="单位" prop="unit">
<el-input v-model="detailEditForm.unit" placeholder="单位" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="detailEditForm.remark" type="textarea" :rows="2" placeholder="备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="detailEditOpen = false">取消</el-button>
<el-button type="primary" :loading="detailEditLoading" @click="submitDetailEdit">保存</el-button>
</template>
</el-dialog>
<!-- 发货计划 新增/编辑弹窗 -->
<el-dialog :title="planTitle" v-model="planOpen" width="520px" append-to-body>
<el-form ref="planFormRef" :model="planForm" label-width="90px" v-loading="planSubmitLoading">
@@ -403,6 +464,10 @@ import {
addShippingOrder,
updateShippingOrder,
delShippingOrder,
listShippingOrderDetail,
addShippingOrderDetail,
updateShippingOrderDetail,
delShippingOrderDetail,
listShippingPlanWithCount,
addShippingPlan,
updateShippingPlan,
@@ -412,9 +477,10 @@ import {
import { listOrder, getOrder } from '@/api/oms/order'
import { getCustomer } from '@/api/oms/customer'
import { listSalesman } from '@/api/oms/salesman'
import { listOrderDetail } from '@/api/oms/orderDetail'
import { getDicts } from '@/api/system/dict/data'
import { listProductAdditionByProductIds } from '@/api/mat/product'
import { listProductBase } from '@/api/mat/product'
import { getProduct as getOaProduct } from '@/api/oa/product'
import * as XLSX from 'xlsx'
import CustomerSelect from '@/components/CustomerSelect/index.vue'
@@ -442,10 +508,21 @@ export default {
// 右侧当前选中行(下方明细区展示)
currentRow: null,
currentOrderInfo: null,
// 当前发货单绑定订单的订单明细(用于页面展示与打印)
// 当前发货单据的明细(用于页面展示与打印)
detailProductsLoading: false,
detailProducts: [],
detailAdditionMap: {},
detailEditOpen: false,
detailEditTitle: '',
detailEditLoading: false,
detailEditForm: {},
detailEditRules: {
productId: [{ required: true, message: '请选择产品', trigger: 'change' }],
quantity: [{ required: true, message: '请输入数量', trigger: 'blur' }],
unit: [{ required: true, message: '请输入单位', trigger: 'blur' }]
},
detailProductOptions: [],
detailProductLoading: false,
salesmanOptions: [],
orderCompanyOptions: [],
// 完成状态选项(对应 gear_shipping_order.status
@@ -685,24 +762,24 @@ export default {
},
handleCurrentChange(row) {
this.currentRow = row || null
// 选择发货单时,加载其绑定订单的订单明细(仅成品)
if (this.currentRow && this.currentRow.orderId) {
this.loadOrderDetailProducts(this.currentRow.orderId)
this.loadOrderInfo(this.currentRow.orderId)
if (this.currentRow && this.currentRow.shippingId) {
this.loadShippingOrderDetailProducts(this.currentRow.shippingId)
} else {
this.detailProducts = []
this.detailAdditionMap = {}
}
if (this.currentRow && this.currentRow.orderId) {
this.loadOrderInfo(this.currentRow.orderId)
} else {
this.currentOrderInfo = null
}
},
/** 发货单绑定订单后:获取对应订单的订单明细产品数据(不包含库存,只取订单明细) */
loadOrderDetailProducts(orderId) {
loadShippingOrderDetailProducts(shippingId) {
this.detailProductsLoading = true
listOrderDetail({ orderId: orderId, pageNum: 1, pageSize: 9999 })
listShippingOrderDetail({ shippingId: shippingId, pageNum: 1, pageSize: 9999 })
.then(res => {
const rows = (res && res.rows) ? res.rows : []
// 仅展示成品product符合“只有成品类型才可以进入发货”
this.detailProducts = rows.filter(r => String(r.productType || '').toLowerCase() === 'product')
this.detailProducts = rows
const ids = this.detailProducts.map(r => r.productId).filter(Boolean)
this.loadProductAdditions(ids).then(map => {
this.detailAdditionMap = map
@@ -747,7 +824,118 @@ export default {
this.$modal.msgSuccess('状态已更新')
})
},
/** 打印预览:打开弹窗并加载绑定订单的订单明细产品数据 */
reloadCurrentDetail() {
if (!this.currentRow || !this.currentRow.shippingId) return
this.loadShippingOrderDetailProducts(this.currentRow.shippingId)
},
resetDetailEdit() {
const shippingId = this.currentRow && this.currentRow.shippingId ? this.currentRow.shippingId : undefined
this.detailEditForm = {
detailId: undefined,
shippingId,
productId: undefined,
productCode: '',
productName: '',
spec: '',
model: '',
quantity: 0,
unit: '',
remark: '',
sort: 0
}
this.detailProductOptions = []
this.resetForm('detailEditFormRef')
},
handleDetailAdd() {
if (!this.currentRow || !this.currentRow.shippingId) {
this.$modal.msgError('请先选择发货单')
return
}
this.resetDetailEdit()
this.detailEditTitle = '新增产品'
this.detailEditOpen = true
},
handleDetailEdit(row) {
if (!row || !row.detailId) return
this.resetDetailEdit()
this.detailEditForm = Object.assign(this.detailEditForm, row)
this.detailEditTitle = '修改产品'
this.detailEditOpen = true
if (row.productId) {
this.detailProductOptions = [{
productId: row.productId,
productName: row.productName,
spec: row.spec,
model: row.model,
productType: 'product'
}]
}
},
handleDetailDelete(row) {
if (!row || !row.detailId) return
this.$modal.confirm('是否确认删除该产品行?').then(() => {
return delShippingOrderDetail(String(row.detailId))
}).then(() => {
this.$modal.msgSuccess('删除成功')
this.reloadCurrentDetail()
})
},
remoteSearchDetailProducts(keyword) {
const q = String(keyword || '').trim()
this.detailProductLoading = true
listProductBase({
pageNum: 1,
pageSize: 50,
productType: 'product',
productName: q
}).then(res => {
this.detailProductOptions = (res && res.rows) ? res.rows : []
}).finally(() => {
this.detailProductLoading = false
})
},
detailProductOptionLabel(p) {
const name = p && p.productName ? p.productName : '-'
const parts = [p && p.spec, p && p.model].filter(Boolean)
return parts.length ? `${name}${parts.join(' / ')}` : name
},
handleDetailProductPicked(productId) {
const id = productId
const hit = (this.detailProductOptions || []).find(p => String(p.productId) === String(id))
if (hit) {
this.detailEditForm.productName = hit.productName || this.detailEditForm.productName
this.detailEditForm.spec = hit.spec || ''
this.detailEditForm.model = hit.model || ''
}
if (id != null && String(id) !== '') {
getOaProduct(id).then(res => {
const d = res && res.data ? res.data : null
if (!d) return
if (d.productCode) this.detailEditForm.productCode = d.productCode
if (!this.detailEditForm.unit && d.unit) this.detailEditForm.unit = d.unit
}).catch(() => {})
}
},
submitDetailEdit() {
if (!this.detailEditForm || !this.detailEditForm.shippingId) {
this.$modal.msgError('请先选择发货单')
return
}
this.$refs.detailEditFormRef.validate(valid => {
if (!valid) return
this.detailEditLoading = true
const payload = Object.assign({}, this.detailEditForm)
const req = payload.detailId ? updateShippingOrderDetail(payload) : addShippingOrderDetail(payload)
req.then(() => {
this.$modal.msgSuccess('保存成功')
this.detailEditOpen = false
this.reloadCurrentDetail()
}).finally(() => {
this.detailEditLoading = false
})
})
},
/** 打印预览:打开弹窗并加载发货单据明细产品数据 */
printPreview(row) {
const target = row || this.currentRow
if (!target || !target.orderId) {
@@ -759,10 +947,10 @@ export default {
this.printLoading = true
Promise.all([
getOrder(target.orderId).then(res => (res && res.data) ? res.data : null).catch(() => null),
listOrderDetail({ orderId: target.orderId, pageNum: 1, pageSize: 9999 }).then(res => (res && res.rows) ? res.rows : []).catch(() => []),
listShippingOrderDetail({ shippingId: target.shippingId, pageNum: 1, pageSize: 9999 }).then(res => (res && res.rows) ? res.rows : []).catch(() => []),
]).then(([orderInfo, rows]) => {
this.printOrderInfo = orderInfo
this.printProducts = rows.filter(r => String(r.productType || '').toLowerCase() === 'product')
this.printProducts = rows
const ids = this.printProducts.map(r => r.productId).filter(Boolean)
return this.loadProductAdditions(ids)
}).then(map => {
@@ -1086,9 +1274,6 @@ export default {
}).then(res => {
this.orderSelectList = (res && res.rows) ? res.rows : []
this.orderSelectTotal = res && res.total ? res.total : 0
if (this.orderSelectList.length === 1) {
this.confirmOrderBind(this.orderSelectList[0])
}
}).finally(() => {
this.orderSelectLoading = false
})
@@ -1098,6 +1283,7 @@ export default {
this.editForm.orderId = row.orderId
this.editForm.orderCode = row.orderCode
this.orderSelectOpen = false
this.$modal.msgSuccess('订单已绑定')
getOrder(row.orderId).then(res => {
const order = res && res.data ? res.data : null
if (!order) return
@@ -1107,9 +1293,23 @@ export default {
} else if (order.customerName) {
this.editForm.receiverCompany = String(order.customerName)
}
}).finally(() => {
this.$modal.msgSuccess('订单已绑定')
})
}).catch(() => {})
},
orderStatusLabel(val) {
const n = val == null || val === '' ? NaN : Number(val)
if (n === 0) return '预订单'
if (n === 1) return '进行中'
if (n === 2) return '已完成'
if (n === 3) return '已取消'
return '—'
},
orderStatusTagType(val) {
const n = val == null || val === '' ? NaN : Number(val)
if (n === 0) return 'info'
if (n === 1) return 'warning'
if (n === 2) return 'success'
if (n === 3) return 'danger'
return 'info'
},
handleReceiverCustomerPicked(customerId) {
if (customerId == null || String(customerId) === '') {

View File

@@ -218,25 +218,30 @@
</el-dialog>
<el-dialog title="物料出入库统计" v-model="flowOpen" width="980px" top="6vh" append-to-body>
<el-form :inline="true" size="small" label-width="90px">
<el-form-item label="物料">
<RawSelector v-model="flowForm.itemId" :allowAdd="false" @change="onFlowMaterialChange" />
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker
v-model="flowForm.timeRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
style="width: 360px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="flowLoading" @click="fetchFlow">查询</el-button>
</el-form-item>
<el-form :inline="true" size="small" label-width="70px">
<div style="display:flex; flex-wrap:wrap; align-items:flex-end; gap:10px 16px;">
<el-form-item label="物料" style="margin-bottom: 0;">
<RawSelector v-model="flowForm.itemId" :allowAdd="false" @change="onFlowMaterialChange" />
</el-form-item>
<el-form-item label="厂家" style="margin-bottom: 0;">
<el-input v-model="flowForm.factory" placeholder="请输入厂家" clearable :disabled="!!flowForm.itemId" style="width: 220px" />
</el-form-item>
<el-form-item label="时间" style="margin-bottom: 0;">
<el-date-picker
v-model="flowForm.timeRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
style="width: 360px"
/>
</el-form-item>
<el-form-item style="margin-bottom: 0; margin-left: auto;">
<el-button type="primary" :loading="flowLoading" @click="fetchFlow">查询</el-button>
</el-form-item>
</div>
</el-form>
<div v-loading="flowLoading" style="margin-top: 10px;">
@@ -245,7 +250,8 @@
物料出入库统计
</div>
<div style="margin-bottom: 10px;">
<span>物料</span><span>{{ flowItemName || '-' }}</span>
<span v-if="flowForm.itemId">物料:</span><span v-if="flowForm.itemId">{{ flowItemName || '-' }}</span>
<span v-else>厂家</span><span v-else>{{ flowForm.factory || '-' }}</span>
<span style="margin-left: 18px;">时间</span>
<span>{{ (flowForm.timeRange && flowForm.timeRange[0]) || '-' }}</span>
<span> </span>
@@ -277,7 +283,7 @@
</el-card>
</el-col>
</el-row>
<el-row :gutter="10" style="margin-bottom: 10px;">
<el-row :gutter="10" justify="center" style="margin-bottom: 10px;">
<el-col :span="6">
<el-card shadow="never">
<div>净变动</div>
@@ -351,6 +357,7 @@ export default {
flowLoading: false,
flowForm: {
itemId: undefined,
factory: undefined,
timeRange: []
},
flowItemName: '',
@@ -515,23 +522,34 @@ export default {
},
onFlowMaterialChange(material) {
this.flowItemName = material && material.materialName ? material.materialName : ''
if (this.flowForm.itemId) {
this.flowForm.factory = undefined
}
},
fetchFlow() {
if (!this.flowForm.itemId) {
this.$modal.msgError('请选择物料')
return
}
if (!this.flowForm.timeRange || this.flowForm.timeRange.length !== 2) {
this.$modal.msgError('请选择时间范围')
return
}
const factory = this.flowForm.factory ? String(this.flowForm.factory).trim() : ''
const itemId = this.flowForm.itemId
if (!itemId && !factory) {
this.$modal.msgError('请选择物料或填写厂家')
return
}
this.flowLoading = true
this.flowResult = null
getMaterialFlow({
itemId: this.flowForm.itemId,
const params = {
startTime: this.flowForm.timeRange[0],
endTime: this.flowForm.timeRange[1]
})
}
if (itemId) {
params.itemId = itemId
} else {
params.factory = factory
this.flowItemName = ''
}
getMaterialFlow(params)
.then((res) => {
this.flowResult = res.data || null
})
@@ -540,22 +558,29 @@ export default {
})
},
exportFlow() {
if (!this.flowForm.itemId) {
this.$modal.msgError('请选择物料')
return
}
if (!this.flowForm.timeRange || this.flowForm.timeRange.length !== 2) {
this.$modal.msgError('请选择时间范围')
return
}
const factory = this.flowForm.factory ? String(this.flowForm.factory).trim() : ''
const itemId = this.flowForm.itemId
if (!itemId && !factory) {
this.$modal.msgError('请选择物料或填写厂家')
return
}
const params = {
startTime: this.flowForm.timeRange[0],
endTime: this.flowForm.timeRange[1]
}
if (itemId) {
params.itemId = itemId
} else {
params.factory = factory
}
this.download(
'/gear/stockIoOrder/materialFlow/export',
{
itemId: this.flowForm.itemId,
startTime: this.flowForm.timeRange[0],
endTime: this.flowForm.timeRange[1]
},
`material_flow_${this.flowForm.itemId}_${new Date().getTime()}.xlsx`
params,
itemId ? `material_flow_${itemId}_${new Date().getTime()}.xlsx` : `material_flow_factory_${factory}_${new Date().getTime()}.xlsx`
)
},
submitEdit() {

View File

@@ -199,25 +199,30 @@
</el-dialog>
<el-dialog title="物料出入库统计" v-model="flowOpen" width="980px" top="6vh" append-to-body>
<el-form :inline="true" size="small" label-width="90px">
<el-form-item label="物料">
<RawSelector v-model="flowForm.itemId" :allowAdd="false" @change="onFlowMaterialChange" />
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker
v-model="flowForm.timeRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
style="width: 360px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="flowLoading" @click="fetchFlow">查询</el-button>
</el-form-item>
<el-form :inline="true" size="small" label-width="70px">
<div style="display:flex; flex-wrap:wrap; align-items:flex-end; gap:10px 16px;">
<el-form-item label="物料" style="margin-bottom: 0;">
<RawSelector v-model="flowForm.itemId" :allowAdd="false" @change="onFlowMaterialChange" />
</el-form-item>
<el-form-item label="厂家" style="margin-bottom: 0;">
<el-input v-model="flowForm.factory" placeholder="请输入厂家" clearable :disabled="!!flowForm.itemId" style="width: 220px" />
</el-form-item>
<el-form-item label="时间" style="margin-bottom: 0;">
<el-date-picker
v-model="flowForm.timeRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
style="width: 360px"
/>
</el-form-item>
<el-form-item style="margin-bottom: 0; margin-left: auto;">
<el-button type="primary" :loading="flowLoading" @click="fetchFlow">查询</el-button>
</el-form-item>
</div>
</el-form>
<div v-loading="flowLoading" style="margin-top: 10px;">
@@ -226,7 +231,8 @@
物料出入库统计
</div>
<div style="margin-bottom: 10px;">
<span>物料</span><span>{{ flowItemName || '-' }}</span>
<span v-if="flowForm.itemId">物料:</span><span v-if="flowForm.itemId">{{ flowItemName || '-' }}</span>
<span v-else>厂家</span><span v-else>{{ flowForm.factory || '-' }}</span>
<span style="margin-left: 18px;">时间</span>
<span>{{ (flowForm.timeRange && flowForm.timeRange[0]) || '-' }}</span>
<span> </span>
@@ -258,7 +264,7 @@
</el-card>
</el-col>
</el-row>
<el-row :gutter="10" style="margin-bottom: 10px;">
<el-row :gutter="10" justify="center" style="margin-bottom: 10px;">
<el-col :span="6">
<el-card shadow="never">
<div>净变动</div>
@@ -330,6 +336,7 @@ export default {
flowLoading: false,
flowForm: {
itemId: undefined,
factory: undefined,
timeRange: []
},
flowItemName: '',
@@ -473,23 +480,34 @@ export default {
},
onFlowMaterialChange(material) {
this.flowItemName = material && material.materialName ? material.materialName : ''
if (this.flowForm.itemId) {
this.flowForm.factory = undefined
}
},
fetchFlow() {
if (!this.flowForm.itemId) {
this.$modal.msgError('请选择物料')
return
}
if (!this.flowForm.timeRange || this.flowForm.timeRange.length !== 2) {
this.$modal.msgError('请选择时间范围')
return
}
const factory = this.flowForm.factory ? String(this.flowForm.factory).trim() : ''
const itemId = this.flowForm.itemId
if (!itemId && !factory) {
this.$modal.msgError('请选择物料或填写厂家')
return
}
this.flowLoading = true
this.flowResult = null
getMaterialFlow({
itemId: this.flowForm.itemId,
const params = {
startTime: this.flowForm.timeRange[0],
endTime: this.flowForm.timeRange[1]
})
}
if (itemId) {
params.itemId = itemId
} else {
params.factory = factory
this.flowItemName = ''
}
getMaterialFlow(params)
.then((res) => {
this.flowResult = res.data || null
})
@@ -498,22 +516,29 @@ export default {
})
},
exportFlow() {
if (!this.flowForm.itemId) {
this.$modal.msgError('请选择物料')
return
}
if (!this.flowForm.timeRange || this.flowForm.timeRange.length !== 2) {
this.$modal.msgError('请选择时间范围')
return
}
const factory = this.flowForm.factory ? String(this.flowForm.factory).trim() : ''
const itemId = this.flowForm.itemId
if (!itemId && !factory) {
this.$modal.msgError('请选择物料或填写厂家')
return
}
const params = {
startTime: this.flowForm.timeRange[0],
endTime: this.flowForm.timeRange[1]
}
if (itemId) {
params.itemId = itemId
} else {
params.factory = factory
}
this.download(
'/gear/stockIoOrder/materialFlow/export',
{
itemId: this.flowForm.itemId,
startTime: this.flowForm.timeRange[0],
endTime: this.flowForm.timeRange[1]
},
`material_flow_${this.flowForm.itemId}_${new Date().getTime()}.xlsx`
params,
itemId ? `material_flow_${itemId}_${new Date().getTime()}.xlsx` : `material_flow_factory_${factory}_${new Date().getTime()}.xlsx`
)
},
submitEdit() {

View File

@@ -293,25 +293,30 @@
</el-dialog>
<el-dialog title="物料出入库统计" v-model="flowOpen" width="980px" top="6vh" append-to-body>
<el-form :inline="true" size="small" label-width="90px">
<el-form-item label="物料">
<RawSelector v-model="flowForm.itemId" :allowAdd="false" @change="onFlowMaterialChange" />
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker
v-model="flowForm.timeRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
style="width: 360px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="flowLoading" @click="fetchFlow">查询</el-button>
</el-form-item>
<el-form :inline="true" size="small" label-width="70px">
<div style="display:flex; flex-wrap:wrap; align-items:flex-end; gap:10px 16px;">
<el-form-item label="物料" style="margin-bottom: 0;">
<RawSelector v-model="flowForm.itemId" :allowAdd="false" @change="onFlowMaterialChange" />
</el-form-item>
<el-form-item label="厂家" style="margin-bottom: 0;">
<el-input v-model="flowForm.factory" placeholder="请输入厂家" clearable :disabled="!!flowForm.itemId" style="width: 220px" />
</el-form-item>
<el-form-item label="时间" style="margin-bottom: 0;">
<el-date-picker
v-model="flowForm.timeRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
style="width: 360px"
/>
</el-form-item>
<el-form-item style="margin-bottom: 0; margin-left: auto;">
<el-button type="primary" :loading="flowLoading" @click="fetchFlow">查询</el-button>
</el-form-item>
</div>
</el-form>
<div v-loading="flowLoading" style="margin-top: 10px;">
@@ -320,7 +325,8 @@
物料出入库统计
</div>
<div style="margin-bottom: 10px;">
<span>物料</span><span>{{ flowItemName || '-' }}</span>
<span v-if="flowForm.itemId">物料:</span><span v-if="flowForm.itemId">{{ flowItemName || '-' }}</span>
<span v-else>厂家</span><span v-else>{{ flowForm.factory || '-' }}</span>
<span style="margin-left: 18px;">时间</span>
<span>{{ (flowForm.timeRange && flowForm.timeRange[0]) || '-' }}</span>
<span> </span>
@@ -352,7 +358,7 @@
</el-card>
</el-col>
</el-row>
<el-row :gutter="10" style="margin-bottom: 10px;">
<el-row :gutter="10" justify="center" style="margin-bottom: 10px;">
<el-col :span="6">
<el-card shadow="never">
<div>净变动</div>
@@ -456,6 +462,7 @@ export default {
flowLoading: false,
flowForm: {
itemId: undefined,
factory: undefined,
timeRange: []
},
flowItemName: '',
@@ -670,23 +677,34 @@ export default {
},
onFlowMaterialChange(material) {
this.flowItemName = material && material.materialName ? material.materialName : ''
if (this.flowForm.itemId) {
this.flowForm.factory = undefined
}
},
fetchFlow() {
if (!this.flowForm.itemId) {
this.$modal.msgError('请选择物料')
return
}
if (!this.flowForm.timeRange || this.flowForm.timeRange.length !== 2) {
this.$modal.msgError('请选择时间范围')
return
}
const factory = this.flowForm.factory ? String(this.flowForm.factory).trim() : ''
const itemId = this.flowForm.itemId
if (!itemId && !factory) {
this.$modal.msgError('请选择物料或填写厂家')
return
}
this.flowLoading = true
this.flowResult = null
getMaterialFlow({
itemId: this.flowForm.itemId,
const params = {
startTime: this.flowForm.timeRange[0],
endTime: this.flowForm.timeRange[1]
})
}
if (itemId) {
params.itemId = itemId
} else {
params.factory = factory
this.flowItemName = ''
}
getMaterialFlow(params)
.then((res) => {
this.flowResult = res.data || null
})
@@ -695,22 +713,29 @@ export default {
})
},
exportFlow() {
if (!this.flowForm.itemId) {
this.$modal.msgError('请选择物料')
return
}
if (!this.flowForm.timeRange || this.flowForm.timeRange.length !== 2) {
this.$modal.msgError('请选择时间范围')
return
}
const factory = this.flowForm.factory ? String(this.flowForm.factory).trim() : ''
const itemId = this.flowForm.itemId
if (!itemId && !factory) {
this.$modal.msgError('请选择物料或填写厂家')
return
}
const params = {
startTime: this.flowForm.timeRange[0],
endTime: this.flowForm.timeRange[1]
}
if (itemId) {
params.itemId = itemId
} else {
params.factory = factory
}
this.download(
'/gear/stockIoOrder/materialFlow/export',
{
itemId: this.flowForm.itemId,
startTime: this.flowForm.timeRange[0],
endTime: this.flowForm.timeRange[1]
},
`material_flow_${this.flowForm.itemId}_${new Date().getTime()}.xlsx`
params,
itemId ? `material_flow_${itemId}_${new Date().getTime()}.xlsx` : `material_flow_factory_${factory}_${new Date().getTime()}.xlsx`
)
},
showDetail(row) {

View File

@@ -42,6 +42,29 @@ CREATE TABLE gear_shipping_order (
KEY idx_ship_time (ship_time)
) ENGINE=InnoDB COMMENT='发货单据表';
DROP TABLE IF EXISTS gear_shipping_order_detail;
CREATE TABLE gear_shipping_order_detail (
detail_id bigint(20) NOT NULL COMMENT '发货单据明细ID',
shipping_id bigint(20) NOT NULL COMMENT '发货单据ID gear_shipping_order.shipping_id',
product_id bigint(20) NOT NULL COMMENT '产品ID',
product_code varchar(64) DEFAULT '' COMMENT '产品编号快照',
product_name varchar(255) DEFAULT '' COMMENT '产品名称快照',
spec varchar(255) DEFAULT '' COMMENT '规格快照',
model varchar(255) DEFAULT '' COMMENT '型号快照',
quantity decimal(18,4) NOT NULL DEFAULT 0 COMMENT '数量',
unit varchar(32) DEFAULT '' COMMENT '单位',
remark varchar(500) DEFAULT NULL COMMENT '备注',
sort int DEFAULT 0 COMMENT '排序',
del_flag char(1) NOT NULL DEFAULT '0' COMMENT '删除标志0存在 2删除',
create_by varchar(64) DEFAULT '' COMMENT '创建者',
create_time datetime COMMENT '创建时间',
update_by varchar(64) DEFAULT '' COMMENT '更新者',
update_time datetime COMMENT '更新时间',
PRIMARY KEY (detail_id),
KEY idx_shipping_id (shipping_id),
KEY idx_product_id (product_id)
) ENGINE=InnoDB COMMENT='发货单据明细表';
-- ================================
-- 发货计划(独立表)
-- 目标用于“发货单据”左侧计划列表计划下可关联多张发货单据gear_shipping_order.plan_id