Procházet zdrojové kódy

订单发货单列表以及所有接口操作日志

Pengxy před 3 měsíci
rodič
revize
393f4cb3d5
28 změnil soubory, kde provedl 1801 přidání a 227 odebrání
  1. 36 0
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/LogRecordConstants.java
  2. 71 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/OrderDeliveryController.java
  3. 32 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/vo/ShipmentListItemVO.java
  4. 30 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/vo/ShipmentPageReqVO.java
  5. 112 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/vo/ShipmentRespVO.java
  6. 143 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/vo/ShipmentSaveReqVO.java
  7. 6 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/vo/ShippingPlanListItemVO.java
  8. 25 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/vo/ShippingPlanSaveReqVO.java
  9. 14 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/order/vo/OrderEntrySaveReqVO.java
  10. 17 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/order/vo/OrderSaveReqVO.java
  11. 123 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/dataobject/ShipmentDetailDO.java
  12. 120 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/dataobject/ShipmentMasterDO.java
  13. 27 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/mysql/ShipmentDetailMapper.java
  14. 61 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/mysql/ShipmentMasterMapper.java
  15. 2 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/enums/ErrorCodeConstants.java
  16. 54 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/ShipmentService.java
  17. 8 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/impl/OrderChangeServiceImpl.java
  18. 22 1
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/impl/OrderDeliveryServiceImpl.java
  19. 21 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/impl/OrderServiceImpl.java
  20. 316 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/impl/ShipmentServiceImpl.java
  21. 66 0
      yudao-order-server/src/main/resources/mapper/order/ShipmentDetailMapper.xml
  22. 149 0
      yudao-order-server/src/main/resources/mapper/order/ShipmentMasterMapper.xml
  23. 3 0
      yudao-order-server/src/main/resources/mapper/order/ShippingPlanMapper.xml
  24. 15 0
      yudao-ui/yudao-ui-admin-vue3/src/api/jiaohuo/order.ts
  25. 51 47
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/OrderShipment.vue
  26. 10 26
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/ShippingPlan.vue
  27. 260 146
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/components/ShipmentForm.vue
  28. 7 7
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/components/ShippingPlanForm.vue

+ 36 - 0
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/LogRecordConstants.java

@@ -30,4 +30,40 @@ public interface LogRecordConstants {
     String SYSTEM_ROLE_DELETE_SUB_TYPE = "删除角色";
     String SYSTEM_ROLE_DELETE_SUCCESS = "删除了角色【{{#role.name}}】";
 
+    // ======================= ORDER_SHIPMENT 发货单 =======================
+
+    String ORDER_SHIPMENT_TYPE = "ORDER 发货单";
+    String ORDER_SHIPMENT_CREATE_SUB_TYPE = "创建发货单";
+    String ORDER_SHIPMENT_CREATE_SUCCESS = "创建了发货单【{{#shipment.id}}】";
+    String ORDER_SHIPMENT_UPDATE_SUB_TYPE = "更新发货单";
+    String ORDER_SHIPMENT_UPDATE_SUCCESS = "更新了发货单【{{#shipment.id}}】: {_DIFF{#updateReqVO}}";
+    String ORDER_SHIPMENT_DELETE_SUB_TYPE = "删除发货单";
+    String ORDER_SHIPMENT_DELETE_SUCCESS = "删除了发货单【{{#shipment.id}}】";
+
+    // ======================= ORDER_CHANGE 订单变更 =======================
+
+    String ORDER_CHANGE_TYPE = "ORDER 订单变更";
+    String ORDER_CHANGE_CREATE_SUB_TYPE = "创建订单变更";
+    String ORDER_CHANGE_CREATE_SUCCESS = "创建了订单变更【{{#orderChange.billNo}}】";
+
+    // ======================= SHIPPING_PLAN 出货计划 =======================
+
+    String SHIPPING_PLAN_TYPE = "ORDER 出货计划";
+    String SHIPPING_PLAN_CREATE_SUB_TYPE = "创建出货计划";
+    String SHIPPING_PLAN_CREATE_SUCCESS = "创建了出货计划【{{#shippingPlan.recId}}】";
+    String SHIPPING_PLAN_UPDATE_SUB_TYPE = "更新出货计划";
+    String SHIPPING_PLAN_UPDATE_SUCCESS = "更新了出货计划【{{#shippingPlan.recId}}】: {_DIFF{#updateReqVO}}";
+    String SHIPPING_PLAN_DELETE_SUB_TYPE = "删除出货计划";
+    String SHIPPING_PLAN_DELETE_SUCCESS = "删除了出货计划【{{#shippingPlan.recId}}】";
+
+    // ======================= SALES_ORDER 销售订单 =======================
+
+    String SALES_ORDER_TYPE = "ORDER 销售订单";
+    String SALES_ORDER_CREATE_SUB_TYPE = "创建销售订单";
+    String SALES_ORDER_CREATE_SUCCESS = "创建了销售订单【{{#salesOrder.billNo}}】";
+    String SALES_ORDER_UPDATE_SUB_TYPE = "更新销售订单";
+    String SALES_ORDER_UPDATE_SUCCESS = "更新了销售订单【{{#salesOrder.billNo}}】: {_DIFF{#updateReqVO}}";
+    String SALES_ORDER_DELETE_SUB_TYPE = "删除销售订单";
+    String SALES_ORDER_DELETE_SUCCESS = "删除了销售订单,ID:{{#ids}}";
+
 }

+ 71 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/OrderDeliveryController.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.order.controller.admin.delivery.vo.*;
 import cn.iocoder.yudao.module.order.service.OrderDeliveryService;
+import cn.iocoder.yudao.module.order.service.ShipmentService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -13,6 +14,9 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
+import java.util.Map;
+
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
 @Tag(name = "管理后台 - 订单交付")
@@ -24,6 +28,9 @@ public class OrderDeliveryController {
     @Resource
     private OrderDeliveryService orderDeliveryService;
 
+    @Resource
+    private ShipmentService shipmentService;
+
     @GetMapping("/delivery/list")
     @Operation(summary = "订单交付列表查询")
     @PreAuthorize("@ss.hasPermission('order:delivery:query')")
@@ -94,4 +101,68 @@ public class OrderDeliveryController {
         orderDeliveryService.salesOutbound(reqVO);
         return success(true);
     }
+
+    // ==================== 发货单管理 ====================
+
+    @GetMapping("/shipment/list")
+    @Operation(summary = "发货单列表查询")
+    @PreAuthorize("@ss.hasPermission('order:shipment:query')")
+    public CommonResult<PageResult<ShipmentListItemVO>> getShipmentList(@Valid ShipmentPageReqVO pageReqVO) {
+        return success(shipmentService.getShipmentListPage(pageReqVO));
+    }
+
+    @GetMapping("/shipment/{id}")
+    @Operation(summary = "获取发货单详情")
+    @Parameter(name = "id", description = "发货单主键", required = true)
+    @PreAuthorize("@ss.hasPermission('order:shipment:query')")
+    public CommonResult<ShipmentRespVO> getShipment(@PathVariable("id") Long id) {
+        return success(shipmentService.getShipment(id));
+    }
+
+    @PostMapping("/shipment")
+    @Operation(summary = "创建发货单")
+    @PreAuthorize("@ss.hasPermission('order:shipment:create')")
+    public CommonResult<Long> createShipment(@Valid @RequestBody ShipmentSaveReqVO createReqVO) {
+        return success(shipmentService.createShipment(createReqVO));
+    }
+
+    @PutMapping("/shipment")
+    @Operation(summary = "更新发货单")
+    @PreAuthorize("@ss.hasPermission('order:shipment:update')")
+    public CommonResult<Boolean> updateShipment(@Valid @RequestBody ShipmentSaveReqVO updateReqVO) {
+        shipmentService.updateShipment(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/shipment/{id}")
+    @Operation(summary = "删除发货单")
+    @Parameter(name = "id", description = "发货单主键", required = true)
+    @PreAuthorize("@ss.hasPermission('order:shipment:delete')")
+    public CommonResult<Boolean> deleteShipment(@PathVariable("id") Long id) {
+        shipmentService.deleteShipment(id);
+        return success(true);
+    }
+
+    // ==================== 发货单下拉列表数据 ====================
+
+    @GetMapping("/shipment/sales-orders")
+    @Operation(summary = "获取销售单号下拉列表")
+    @PreAuthorize("@ss.hasPermission('order:shipment:query')")
+    public CommonResult<List<Map<String, Object>>> getSalesOrderOptions() {
+        return success(shipmentService.getSalesOrderOptions());
+    }
+
+    @GetMapping("/shipment/customers")
+    @Operation(summary = "获取客户下拉列表")
+    @PreAuthorize("@ss.hasPermission('order:shipment:query')")
+    public CommonResult<List<Map<String, Object>>> getCustomerOptions() {
+        return success(shipmentService.getCustomerOptions());
+    }
+
+    @GetMapping("/shipment/departments")
+    @Operation(summary = "获取部门下拉列表")
+    @PreAuthorize("@ss.hasPermission('order:shipment:query')")
+    public CommonResult<List<Map<String, Object>>> getDepartmentOptions() {
+        return success(shipmentService.getDepartmentOptions());
+    }
 }

+ 32 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/vo/ShipmentListItemVO.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.order.controller.admin.delivery.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 发货单列表项")
+@Data
+public class ShipmentListItemVO {
+
+    @Schema(description = "主键ID")
+    private Long id;
+
+    @Schema(description = "货运单号")
+    private String id1;
+
+    @Schema(description = "订单号")
+    private String ordNbr;
+
+    @Schema(description = "部门名称")
+    private String departmentName;
+
+    @Schema(description = "发货日期")
+    private LocalDateTime shipDate;
+
+    @Schema(description = "状态")
+    private String status;
+
+    @Schema(description = "备注")
+    private String remark;
+}

+ 30 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/vo/ShipmentPageReqVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.order.controller.admin.delivery.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "管理后台 - 发货单分页请求")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ShipmentPageReqVO extends PageParam {
+
+    @Schema(description = "货运单号")
+    private String id;
+
+    @Schema(description = "订单号")
+    private String ordNbr;
+
+    @Schema(description = "状态")
+    private String status;
+
+    @Schema(description = "发货日期开始")
+    private String shipDateStart;
+
+    @Schema(description = "发货日期结束")
+    private String shipDateEnd;
+
+    @Schema(description = "部门")
+    private String department;
+}

+ 112 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/vo/ShipmentRespVO.java

@@ -0,0 +1,112 @@
+package cn.iocoder.yudao.module.order.controller.admin.delivery.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 发货单详情响应")
+@Data
+public class ShipmentRespVO {
+
+    @Schema(description = "主键ID")
+    private Long recId;
+
+    @Schema(description = "货运单号")
+    private String id;
+
+    @Schema(description = "订单号")
+    private String ordNbr;
+
+    @Schema(description = "发货日期")
+    private LocalDateTime shipDate;
+
+    @Schema(description = "发货时间")
+    private String shipTime;
+
+    @Schema(description = "状态")
+    private String status;
+
+    @Schema(description = "地点")
+    private String site;
+
+    @Schema(description = "部门")
+    private String department;
+
+    @Schema(description = "备注")
+    private String remark;
+
+    @Schema(description = "批序号")
+    private String lotSerial;
+
+    @Schema(description = "库位")
+    private String location;
+
+    @Schema(description = "毛重")
+    private BigDecimal grossWeight;
+
+    @Schema(description = "净重")
+    private BigDecimal netWeight;
+
+    @Schema(description = "体积")
+    private BigDecimal volume;
+
+    @Schema(description = "发运数量")
+    private BigDecimal qtyToShip;
+
+    @Schema(description = "客户订单号")
+    private String custPo;
+
+    @Schema(description = "销往")
+    private String soldTo;
+
+    @Schema(description = "发货明细")
+    private List<ShipmentDetailRespVO> items;
+
+    @Data
+    public static class ShipmentDetailRespVO {
+        @Schema(description = "明细主键")
+        private Long recId;
+
+        @Schema(description = "项次")
+        private Integer line;
+
+        @Schema(description = "订单号")
+        private String ordNbr;
+
+        @Schema(description = "订单项次")
+        private Integer ordLine;
+
+        @Schema(description = "集装箱零件/物料编号")
+        private String containerItem;
+
+        @Schema(description = "描述")
+        private String descr;
+
+        @Schema(description = "单位")
+        private String um;
+
+        @Schema(description = "库位")
+        private String location;
+
+        @Schema(description = "批序号")
+        private String lotSerial;
+
+        @Schema(description = "发运数量")
+        private BigDecimal qtyToShip;
+
+        @Schema(description = "挑选数量")
+        private BigDecimal pickingQty;
+
+        @Schema(description = "实际数量")
+        private BigDecimal realQty;
+
+        @Schema(description = "状态")
+        private String status;
+
+        @Schema(description = "备注")
+        private String remark;
+    }
+}

+ 143 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/vo/ShipmentSaveReqVO.java

@@ -0,0 +1,143 @@
+package cn.iocoder.yudao.module.order.controller.admin.delivery.vo;
+
+import com.mzt.logapi.starter.annotation.DiffLogField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "管理后台 - 发货单保存请求")
+@Data
+public class ShipmentSaveReqVO {
+
+    @Schema(description = "主键ID(编辑时必填)")
+    private Long recId;
+
+    @Schema(description = "货运单号")
+    @DiffLogField(name = "货运单号")
+    private String id;
+
+    @Schema(description = "订单号")
+    @DiffLogField(name = "订单号")
+    private String ordNbr;
+
+    @Schema(description = "发货日期", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotBlank(message = "发货日期不能为空")
+    @DiffLogField(name = "发货日期")
+    private String shipDate;
+
+    @Schema(description = "发货时间")
+    @DiffLogField(name = "发货时间")
+    private String shipTime;
+
+    @Schema(description = "状态")
+    @DiffLogField(name = "状态")
+    private String status;
+
+    @Schema(description = "地点")
+    @DiffLogField(name = "地点")
+    private String site;
+
+    @Schema(description = "部门")
+    @DiffLogField(name = "部门")
+    private String department;
+
+    @Schema(description = "备注")
+    @DiffLogField(name = "备注")
+    private String remark;
+
+    @Schema(description = "批序号")
+    @DiffLogField(name = "批序号")
+    private String lotSerial;
+
+    @Schema(description = "库位")
+    @DiffLogField(name = "库位")
+    private String location;
+
+    @Schema(description = "毛重")
+    @DiffLogField(name = "毛重")
+    private BigDecimal grossWeight;
+
+    @Schema(description = "净重")
+    @DiffLogField(name = "净重")
+    private BigDecimal netWeight;
+
+    @Schema(description = "体积")
+    @DiffLogField(name = "体积")
+    private BigDecimal volume;
+
+    @Schema(description = "发运数量")
+    @DiffLogField(name = "发运数量")
+    private BigDecimal qtyToShip;
+
+    @Schema(description = "客户订单号")
+    @DiffLogField(name = "客户订单号")
+    private String custPo;
+
+    @Schema(description = "销往")
+    @DiffLogField(name = "销往")
+    private String soldTo;
+
+    @Schema(description = "发货明细")
+    private List<ShipmentDetailSaveReqVO> items;
+
+    @Data
+    public static class ShipmentDetailSaveReqVO {
+        @Schema(description = "明细主键")
+        private Long recId;
+
+        @Schema(description = "项次")
+        @DiffLogField(name = "项次")
+        private Integer line;
+
+        @Schema(description = "订单号")
+        @DiffLogField(name = "订单号")
+        private String ordNbr;
+
+        @Schema(description = "订单项次")
+        @DiffLogField(name = "订单项次")
+        private Integer ordLine;
+
+        @Schema(description = "集装箱零件/物料编号")
+        @DiffLogField(name = "物料编号")
+        private String containerItem;
+
+        @Schema(description = "描述")
+        @DiffLogField(name = "描述")
+        private String descr;
+
+        @Schema(description = "单位")
+        @DiffLogField(name = "单位")
+        private String um;
+
+        @Schema(description = "库位")
+        @DiffLogField(name = "库位")
+        private String location;
+
+        @Schema(description = "批序号")
+        @DiffLogField(name = "批序号")
+        private String lotSerial;
+
+        @Schema(description = "发运数量")
+        @DiffLogField(name = "发运数量")
+        private BigDecimal qtyToShip;
+
+        @Schema(description = "挑选数量")
+        @DiffLogField(name = "挑选数量")
+        private BigDecimal pickingQty;
+
+        @Schema(description = "实际数量")
+        @DiffLogField(name = "实际数量")
+        private BigDecimal realQty;
+
+        @Schema(description = "状态")
+        @DiffLogField(name = "状态")
+        private String status;
+
+        @Schema(description = "备注")
+        @DiffLogField(name = "备注")
+        private String remark;
+    }
+}

+ 6 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/vo/ShippingPlanListItemVO.java

@@ -61,6 +61,12 @@ public class ShippingPlanListItemVO {
     @Schema(description = "订单数量")
     private BigDecimal qty;
 
+    @Schema(description = "订单项次")
+    private String ordLine;
+
+    @Schema(description = "单位")
+    private String um;
+
     @Schema(description = "包装要求")
     private String packaging;
 

+ 25 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/delivery/vo/ShippingPlanSaveReqVO.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.order.controller.admin.delivery.vo;
 
+import com.mzt.logapi.starter.annotation.DiffLogField;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
@@ -21,34 +22,43 @@ public class ShippingPlanSaveReqVO {
 
     @Schema(description = "出货编号", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotBlank(message = "出货编号不能为空")
+    @DiffLogField(name = "出货编号")
     private String lotSerial;
 
     @Schema(description = "备注")
+    @DiffLogField(name = "备注")
     private String remark;
 
     @Schema(description = "出货地点", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotBlank(message = "出货地点不能为空")
+    @DiffLogField(name = "出货地点")
     private String shippingSite;
 
     @Schema(description = "状态")
+    @DiffLogField(name = "状态")
     private String status;
 
     @Schema(description = "优先级")
+    @DiffLogField(name = "优先级")
     private Integer priority;
 
     @Schema(description = "出货日期", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotBlank(message = "出货日期不能为空")
+    @DiffLogField(name = "出货日期")
     private String shippingDate;
 
     @Schema(description = "收货地址")
+    @DiffLogField(name = "收货地址")
     private String shippingAddress;
 
     @Schema(description = "收货人", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotBlank(message = "收货人不能为空")
+    @DiffLogField(name = "收货人")
     private String consignee;
 
     @Schema(description = "联系方式", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotBlank(message = "联系方式不能为空")
+    @DiffLogField(name = "联系方式")
     private String telephone;
 
     @Schema(description = "出货明细")
@@ -64,18 +74,23 @@ public class ShippingPlanSaveReqVO {
         private String domain;
 
         @Schema(description = "备注")
+        @DiffLogField(name = "备注")
         private String remark;
 
         @Schema(description = "状态")
+        @DiffLogField(name = "状态")
         private String status;
 
         @Schema(description = "客户订单号")
+        @DiffLogField(name = "客户订单号")
         private String ordNbr;
 
         @Schema(description = "订单号")
+        @DiffLogField(name = "订单号")
         private String billNo;
 
         @Schema(description = "物料编号")
+        @DiffLogField(name = "物料编号")
         private String itemNum;
 
         @Schema(description = "销售订单ID")
@@ -85,33 +100,43 @@ public class ShippingPlanSaveReqVO {
         private String sentryId;
 
         @Schema(description = "下单日期")
+        @DiffLogField(name = "下单日期")
         private String ordDate;
 
         @Schema(description = "国家")
+        @DiffLogField(name = "国家")
         private String country;
 
         @Schema(description = "客户编码")
+        @DiffLogField(name = "客户编码")
         private String customNo;
 
         @Schema(description = "客户名称")
+        @DiffLogField(name = "客户名称")
         private String customName;
 
         @Schema(description = "物料名称")
+        @DiffLogField(name = "物料名称")
         private String itemName;
 
         @Schema(description = "规格型号")
+        @DiffLogField(name = "规格型号")
         private String specification;
 
         @Schema(description = "订单数量")
+        @DiffLogField(name = "订单数量")
         private BigDecimal qty;
 
         @Schema(description = "包装要求")
+        @DiffLogField(name = "包装要求")
         private String packaging;
 
         @Schema(description = "重量(KG)")
+        @DiffLogField(name = "重量")
         private BigDecimal weight;
 
         @Schema(description = "体积(M3)")
+        @DiffLogField(name = "体积")
         private BigDecimal volume;
     }
 }

+ 14 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/order/vo/OrderEntrySaveReqVO.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.order.controller.admin.order.vo;
 
+import com.mzt.logapi.starter.annotation.DiffLogField;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
@@ -14,44 +15,57 @@ public class OrderEntrySaveReqVO {
     private Long id;
 
     @Schema(description = "行号")
+    @DiffLogField(name = "行号")
     private Integer entrySeq;
 
     @Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotNull(message = "产品编码不能为空")
+    @DiffLogField(name = "产品编码")
     private String itemNumber;
 
     @Schema(description = "产品名称")
+    @DiffLogField(name = "产品名称")
     private String itemName;
 
     @Schema(description = "规格型号")
+    @DiffLogField(name = "规格型号")
     private String specification;
 
     @Schema(description = "单位")
+    @DiffLogField(name = "单位")
     private String unit;
 
     @Schema(description = "订单数量")
     @NotNull(message = "订单数量不能为空")
+    @DiffLogField(name = "订单数量")
     private BigDecimal qty;
 
     @Schema(description = "含税单价")
+    @DiffLogField(name = "含税单价")
     private BigDecimal taxPrice;
 
     @Schema(description = "价税合计")
+    @DiffLogField(name = "价税合计")
     private BigDecimal totalAmount;
 
     @Schema(description = "客户要求交期")
+    @DiffLogField(name = "客户要求交期")
     private LocalDateTime planDate;
 
     @Schema(description = "最终交货日期")
+    @DiffLogField(name = "最终交货日期")
     private LocalDateTime date;
 
     @Schema(description = "备注")
+    @DiffLogField(name = "备注")
     private String remark;
 
     @Schema(description = "客户订单号")
+    @DiffLogField(name = "客户订单号")
     private String customOrderBillNo;
 
     @Schema(description = "客户订单行号")
+    @DiffLogField(name = "客户订单行号")
     private String customOrderEntryid;
 }
 

+ 17 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/order/vo/OrderSaveReqVO.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.order.controller.admin.order.vo;
 
+import com.mzt.logapi.starter.annotation.DiffLogField;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotBlank;
@@ -18,50 +19,65 @@ public class OrderSaveReqVO {
 
     @Schema(description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotBlank(message = "订单编号不能为空")
+    @DiffLogField(name = "订单编号")
     private String billNo;
 
     @Schema(description = "订单类别")
+    @DiffLogField(name = "订单类别")
     private String orderType;
 
     @Schema(description = "客户编码", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotBlank(message = "客户编码不能为空")
+    @DiffLogField(name = "客户编码")
     private String customNo;
 
     @Schema(description = "客户名称")
+    @DiffLogField(name = "客户名称")
     private String customName;
 
     @Schema(description = "客户级别")
+    @DiffLogField(name = "客户级别")
     private String customLevel;
 
     @Schema(description = "签订日期")
     @NotNull(message = "签订日期不能为空")
+    @DiffLogField(name = "签订日期")
     private LocalDateTime date;
 
     @Schema(description = "采购下单日期")
+    @DiffLogField(name = "采购下单日期")
     private LocalDateTime rdate;
 
     @Schema(description = "加急级别")
+    @DiffLogField(name = "加急级别")
     private String urgent;
 
     @Schema(description = "币种")
+    @DiffLogField(name = "币种")
     private String currency;
 
     @Schema(description = "汇率")
+    @DiffLogField(name = "汇率")
     private BigDecimal exchangeRate;
 
     @Schema(description = "销售部门名称")
+    @DiffLogField(name = "销售部门名称")
     private String saleDeptName;
 
     @Schema(description = "销售部门编码")
+    @DiffLogField(name = "销售部门编码")
     private String saleDeptCode;
 
     @Schema(description = "业务员")
+    @DiffLogField(name = "业务员")
     private String empName;
 
     @Schema(description = "审核人")
+    @DiffLogField(name = "审核人")
     private String auditor;
 
     @Schema(description = "备注")
+    @DiffLogField(name = "备注")
     private String reason;
 
     @Schema(description = "租户")
@@ -77,6 +93,7 @@ public class OrderSaveReqVO {
     private Long companyId;
 
     @Schema(description = "流程状态")
+    @DiffLogField(name = "流程状态")
     private String flowstate;
 
     @Schema(description = "明细行", requiredMode = Schema.RequiredMode.REQUIRED)

+ 123 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/dataobject/ShipmentDetailDO.java

@@ -0,0 +1,123 @@
+package cn.iocoder.yudao.module.order.dal.dataobject;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 销售发货明细表 DO
+ */
+@TableName("ASNBOLShipperDetail")
+@Data
+public class ShipmentDetailDO {
+
+    @TableId(value = "RecID", type = IdType.AUTO)
+    private Long recId;
+
+    @TableField("Domain")
+    private String domain;
+
+    @TableField("Id")
+    private String id;
+
+    @TableField("Line")
+    private Integer line;
+
+    @TableField("OrdNbr")
+    private String ordNbr;
+
+    @TableField("OrdLine")
+    private Integer ordLine;
+
+    @TableField("ContainerItem")
+    private String containerItem;
+
+    @TableField("Descr")
+    private String descr;
+
+    @TableField("UM")
+    private String um;
+
+    @TableField("Location")
+    private String location;
+
+    @TableField("LotSerial")
+    private String lotSerial;
+
+    @TableField("QtyToShip")
+    private BigDecimal qtyToShip;
+
+    @TableField("PickingQty")
+    private BigDecimal pickingQty;
+
+    @TableField("RealQty")
+    private BigDecimal realQty;
+
+    @TableField("Status")
+    private String status;
+
+    @TableField("Remark")
+    private String remark;
+
+    @TableField("ShType")
+    private String shType;
+
+    @TableField("Typed")
+    private String typed;
+
+    @TableField("IsActive")
+    private Boolean isActive;
+
+    @TableField("IsConfirm")
+    private Boolean isConfirm;
+
+    @TableField("CreateUser")
+    private String createUser;
+
+    @TableField("CreateTime")
+    private LocalDateTime createTime;
+
+    @TableField("UpdateUser")
+    private String updateUser;
+
+    @TableField("UpdateTime")
+    private LocalDateTime updateTime;
+
+    @TableField("ASNBOLShipperRecID")
+    private Long asnbolShipperRecId;
+
+    @TableField("ShipDate")
+    private LocalDateTime shipDate;
+
+    @TableField("CustPO")
+    private String custPo;
+
+    @TableField("Price")
+    private BigDecimal price;
+
+    @TableField("GrossWeight")
+    private BigDecimal grossWeight;
+
+    @TableField("NetWeight")
+    private BigDecimal netWeight;
+
+    @TableField("Volume")
+    private BigDecimal volume;
+
+    @TableField("ShipFromId")
+    private String shipFromId;
+
+    @TableField("ShipToId")
+    private String shipToId;
+
+    @TableField("Dimension1")
+    private String dimension1;
+
+    @TableField("Dimension2")
+    private String dimension2;
+}

+ 120 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/dataobject/ShipmentMasterDO.java

@@ -0,0 +1,120 @@
+package cn.iocoder.yudao.module.order.dal.dataobject;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 销售发货主表 DO
+ */
+@TableName("ASNBOLShipperMaster")
+@Data
+public class ShipmentMasterDO {
+
+    @TableId(value = "RecID", type = IdType.AUTO)
+    private Long recId;
+
+    @TableField("Domain")
+    private String domain;
+
+    @TableField("Id")
+    private String id;
+
+    @TableField("OrdNbr")
+    private String ordNbr;
+
+    @TableField("ShipDate")
+    private LocalDateTime shipDate;
+
+    @TableField("ShipTime")
+    private String shipTime;
+
+    @TableField("Status")
+    private String status;
+
+    @TableField("Site")
+    private String site;
+
+    @TableField("Department")
+    private String department;
+
+    @TableField("Remark")
+    private String remark;
+
+    @TableField("ShType")
+    private String shType;
+
+    @TableField("Typed")
+    private String typed;
+
+    @TableField("IsActive")
+    private Boolean isActive;
+
+    @TableField("IsConfirm")
+    private Boolean isConfirm;
+
+    @TableField("CreateUser")
+    private String createUser;
+
+    @TableField("CreateTime")
+    private LocalDateTime createTime;
+
+    @TableField("UpdateUser")
+    private String updateUser;
+
+    @TableField("UpdateTime")
+    private LocalDateTime updateTime;
+
+    @TableField("LotSerial")
+    private String lotSerial;
+
+    @TableField("Location")
+    private String location;
+
+    @TableField("GrossWeight")
+    private BigDecimal grossWeight;
+
+    @TableField("NetWeight")
+    private BigDecimal netWeight;
+
+    @TableField("Volume")
+    private BigDecimal volume;
+
+    @TableField("WeightUM")
+    private String weightUm;
+
+    @TableField("VolumeUM")
+    private String volumeUm;
+
+    @TableField("QtyToShip")
+    private BigDecimal qtyToShip;
+
+    @TableField("QtyShipped")
+    private BigDecimal qtyShipped;
+
+    @TableField("CustPO")
+    private String custPo;
+
+    @TableField("SoldTo")
+    private String soldTo;
+
+    @TableField("ShipFromId")
+    private String shipFromId;
+
+    @TableField("ShipToId")
+    private String shipToId;
+
+    @TableField("Descr")
+    private String descr;
+
+    @TableField("Curr")
+    private String curr;
+
+    @TableField("Price")
+    private BigDecimal price;
+}

+ 27 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/mysql/ShipmentDetailMapper.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.order.dal.mysql;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.order.dal.dataobject.ShipmentDetailDO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface ShipmentDetailMapper extends BaseMapperX<ShipmentDetailDO> {
+
+    /**
+     * 根据主表ID查询明细列表
+     */
+    List<ShipmentDetailDO> selectDetailsByMasterId(@Param("id") String id, @Param("domain") String domain);
+
+    /**
+     * 根据主表ID删除明细
+     */
+    int deleteByMasterId(@Param("id") String id, @Param("domain") String domain);
+
+    /**
+     * 插入发货单明细
+     */
+    int insert(ShipmentDetailDO entity);
+}

+ 61 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/mysql/ShipmentMasterMapper.java

@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.order.dal.mysql;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
+import cn.iocoder.yudao.module.order.controller.admin.delivery.vo.ShipmentListItemVO;
+import cn.iocoder.yudao.module.order.controller.admin.delivery.vo.ShipmentPageReqVO;
+import cn.iocoder.yudao.module.order.dal.dataobject.ShipmentMasterDO;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface ShipmentMasterMapper extends BaseMapperX<ShipmentMasterDO> {
+
+    /**
+     * 发货单列表分页查询
+     */
+    Page<ShipmentListItemVO> selectShipmentList(Page<ShipmentListItemVO> page,
+                                                 @Param("req") ShipmentPageReqVO req);
+
+    default PageResult<ShipmentListItemVO> selectShipmentListPage(ShipmentPageReqVO reqVO) {
+        Page<ShipmentListItemVO> page = MyBatisUtils.buildPage(reqVO);
+        IPage<ShipmentListItemVO> result = selectShipmentList(page, reqVO);
+        return new PageResult<>(result.getRecords(), result.getTotal());
+    }
+
+    /**
+     * 根据RecID查询发货单
+     */
+    ShipmentMasterDO selectByRecId(@Param("recId") Long recId);
+
+    /**
+     * 获取销售单号下拉列表
+     */
+    List<Map<String, Object>> selectSalesOrderOptions();
+
+    /**
+     * 获取客户下拉列表
+     */
+    List<Map<String, Object>> selectCustomerOptions();
+
+    /**
+     * 获取部门下拉列表
+     */
+    List<Map<String, Object>> selectDepartmentOptions();
+
+    /**
+     * 插入发货单主表
+     */
+    int insert(ShipmentMasterDO entity);
+
+    /**
+     * 更新发货单主表
+     */
+    int updateById(ShipmentMasterDO entity);
+}

+ 2 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/enums/ErrorCodeConstants.java

@@ -9,5 +9,7 @@ public interface ErrorCodeConstants {
 
     ErrorCode ORDER_NOT_EXISTS = new ErrorCode(120000, "销售订单不存在");
     ErrorCode SHIPPING_PLAN_NOT_EXISTS = new ErrorCode(120001, "出货计划不存在");
+    ErrorCode SHIPMENT_NOT_EXISTS = new ErrorCode(120002, "发货单不存在");
+    ErrorCode SHIPMENT_STATUS_CLOSED = new ErrorCode(120003, "发货单已关闭,不能操作");
 }
 

+ 54 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/ShipmentService.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.order.service;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.order.controller.admin.delivery.vo.*;
+import jakarta.validation.Valid;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 发货单 Service 接口
+ */
+public interface ShipmentService {
+
+    /**
+     * 发货单列表分页查询
+     */
+    PageResult<ShipmentListItemVO> getShipmentListPage(ShipmentPageReqVO pageReqVO);
+
+    /**
+     * 获取发货单详情
+     */
+    ShipmentRespVO getShipment(Long id);
+
+    /**
+     * 创建发货单
+     */
+    Long createShipment(@Valid ShipmentSaveReqVO createReqVO);
+
+    /**
+     * 更新发货单
+     */
+    void updateShipment(@Valid ShipmentSaveReqVO updateReqVO);
+
+    /**
+     * 删除发货单
+     */
+    void deleteShipment(Long id);
+
+    /**
+     * 获取销售单号下拉列表
+     */
+    List<Map<String, Object>> getSalesOrderOptions();
+
+    /**
+     * 获取客户下拉列表
+     */
+    List<Map<String, Object>> getCustomerOptions();
+
+    /**
+     * 获取部门下拉列表
+     */
+    List<Map<String, Object>> getDepartmentOptions();
+}

+ 8 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/impl/OrderChangeServiceImpl.java

@@ -15,6 +15,8 @@ import cn.iocoder.yudao.module.order.dal.mysql.OrderChangeMapper;
 import cn.iocoder.yudao.module.order.dal.mysql.OrderEntryMapper;
 import cn.iocoder.yudao.module.order.dal.mysql.OrderMapper;
 import cn.iocoder.yudao.module.order.service.OrderChangeService;
+import com.mzt.logapi.context.LogRecordContext;
+import com.mzt.logapi.starter.annotation.LogRecord;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -27,6 +29,7 @@ import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.order.enums.ErrorCodeConstants.ORDER_NOT_EXISTS;
+import static cn.iocoder.yudao.module.system.enums.LogRecordConstants.*;
 
 /**
  * 订单变更流程 Service 实现类
@@ -58,6 +61,8 @@ public class OrderChangeServiceImpl implements OrderChangeService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     @TenantIgnore
+    @LogRecord(type = ORDER_CHANGE_TYPE, subType = ORDER_CHANGE_CREATE_SUB_TYPE, bizNo = "{{#orderChange.id}}",
+            success = ORDER_CHANGE_CREATE_SUCCESS)
     public Long createOrderChange(Long userId, OrderChangeCreateReqVO createReqVO) {
         // 1. 校验订单是否存在
         OrderDO order = orderMapper.selectById(createReqVO.getOrderId());
@@ -123,6 +128,9 @@ public class OrderChangeServiceImpl implements OrderChangeService {
         updateChange.setProcessInstanceId(processInstanceId);
         orderChangeMapper.updateById(updateChange);
 
+        // 7. 记录操作日志上下文
+        LogRecordContext.putVariable("orderChange", changeDO);
+
         return changeDO.getId();
     }
 

+ 22 - 1
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/impl/OrderDeliveryServiceImpl.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.order.service.impl;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.order.controller.admin.delivery.vo.*;
@@ -13,6 +14,9 @@ import cn.iocoder.yudao.module.order.dal.dataobject.ShippingPlanDetailDO;
 import cn.iocoder.yudao.module.order.dal.mysql.*;
 import cn.iocoder.yudao.module.order.enums.ErrorCodeConstants;
 import cn.iocoder.yudao.module.order.service.OrderDeliveryService;
+import com.mzt.logapi.context.LogRecordContext;
+import com.mzt.logapi.service.impl.DiffParseFunction;
+import com.mzt.logapi.starter.annotation.LogRecord;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -23,6 +27,7 @@ import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.module.system.enums.LogRecordConstants.*;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 
@@ -136,6 +141,8 @@ public class OrderDeliveryServiceImpl implements OrderDeliveryService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     @TenantIgnore
+    @LogRecord(type = SHIPPING_PLAN_TYPE, subType = SHIPPING_PLAN_CREATE_SUB_TYPE, bizNo = "{{#shippingPlan.recId}}",
+            success = SHIPPING_PLAN_CREATE_SUCCESS)
     public Long createShippingPlan(ShippingPlanSaveReqVO createReqVO) {
         ShippingPlanDO plan = OrderDeliveryConvert.INSTANCE.convert(createReqVO);
         LocalDateTime now = LocalDateTime.now();
@@ -163,12 +170,17 @@ public class OrderDeliveryServiceImpl implements OrderDeliveryService {
 
         shippingPlanMapper.insert(plan);
         saveDetails(plan, createReqVO.getItems());
+
+        // 记录操作日志上下文
+        LogRecordContext.putVariable("shippingPlan", plan);
         return plan.getRecId();
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     @TenantIgnore
+    @LogRecord(type = SHIPPING_PLAN_TYPE, subType = SHIPPING_PLAN_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.recId}}",
+            success = SHIPPING_PLAN_UPDATE_SUCCESS)
     public void updateShippingPlan(ShippingPlanSaveReqVO updateReqVO) {
         ShippingPlanDO exists = validateShippingPlanExists(updateReqVO.getRecId());
         ShippingPlanDO update = OrderDeliveryConvert.INSTANCE.convert(updateReqVO);
@@ -187,15 +199,24 @@ public class OrderDeliveryServiceImpl implements OrderDeliveryService {
         // 删除旧明细,重新插入
         shippingPlanDetailMapper.deleteByPlanId(exists.getRecId());
         saveDetails(update, updateReqVO.getItems());
+
+        // 记录操作日志上下文
+        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(exists, ShippingPlanSaveReqVO.class));
+        LogRecordContext.putVariable("shippingPlan", exists);
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     @TenantIgnore
+    @LogRecord(type = SHIPPING_PLAN_TYPE, subType = SHIPPING_PLAN_DELETE_SUB_TYPE, bizNo = "{{#id}}",
+            success = SHIPPING_PLAN_DELETE_SUCCESS)
     public void deleteShippingPlan(Long id) {
-        validateShippingPlanExists(id);
+        ShippingPlanDO exists = validateShippingPlanExists(id);
         shippingPlanMapper.deleteById(id);
         shippingPlanDetailMapper.deleteByPlanId(id);
+
+        // 记录操作日志上下文
+        LogRecordContext.putVariable("shippingPlan", exists);
     }
 
     @Override

+ 21 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/impl/OrderServiceImpl.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.order.service.impl;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.order.controller.admin.order.vo.OrderEntrySaveReqVO;
 import cn.iocoder.yudao.module.order.controller.admin.order.vo.OrderRespVO;
@@ -15,6 +16,9 @@ import cn.iocoder.yudao.module.order.dal.mysql.OrderEntryMapper;
 import cn.iocoder.yudao.module.order.dal.mysql.OrderMapper;
 import cn.iocoder.yudao.module.order.enums.ErrorCodeConstants;
 import cn.iocoder.yudao.module.order.service.OrderService;
+import com.mzt.logapi.context.LogRecordContext;
+import com.mzt.logapi.service.impl.DiffParseFunction;
+import com.mzt.logapi.starter.annotation.LogRecord;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -29,6 +33,7 @@ import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.module.system.enums.LogRecordConstants.*;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 
@@ -45,6 +50,8 @@ public class OrderServiceImpl implements OrderService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    @LogRecord(type = SALES_ORDER_TYPE, subType = SALES_ORDER_CREATE_SUB_TYPE, bizNo = "{{#salesOrder.id}}",
+            success = SALES_ORDER_CREATE_SUCCESS)
     public Long createOrder(OrderSaveReqVO createReqVO) {
         OrderDO order = OrderConvert.INSTANCE.convert(createReqVO);
         // 自动填入创建人和创建时间
@@ -60,11 +67,16 @@ public class OrderServiceImpl implements OrderService {
         }
         orderMapper.insert(order);
         saveEntries(order, createReqVO.getItems(), loginUserId, now);
+
+        // 记录操作日志上下文
+        LogRecordContext.putVariable("salesOrder", order);
         return order.getId();
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    @LogRecord(type = SALES_ORDER_TYPE, subType = SALES_ORDER_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
+            success = SALES_ORDER_UPDATE_SUCCESS)
     public void updateOrder(OrderSaveReqVO updateReqVO) {
         OrderDO exists = validateExists(updateReqVO.getId());
         OrderDO update = OrderConvert.INSTANCE.convert(updateReqVO);
@@ -72,16 +84,25 @@ public class OrderServiceImpl implements OrderService {
         orderMapper.updateById(update);
         // 明细:传入 id 匹配更新;传入 id 不存在则新增;未传入但数据库存在则删除
         upsertEntries(update, updateReqVO.getItems());
+
+        // 记录操作日志上下文
+        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(exists, OrderSaveReqVO.class));
+        LogRecordContext.putVariable("salesOrder", exists);
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    @LogRecord(type = SALES_ORDER_TYPE, subType = SALES_ORDER_DELETE_SUB_TYPE, bizNo = "{{#ids}}",
+            success = SALES_ORDER_DELETE_SUCCESS)
     public void deleteOrders(List<Long> ids) {
         if (CollUtil.isEmpty(ids)) {
             return;
         }
         orderMapper.deleteBatchIds(ids);
         ids.forEach(orderEntryMapper::deleteByOrderId);
+
+        // 记录操作日志上下文
+        LogRecordContext.putVariable("ids", ids);
     }
 
     @Override

+ 316 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/impl/ShipmentServiceImpl.java

@@ -0,0 +1,316 @@
+package cn.iocoder.yudao.module.order.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
+import cn.iocoder.yudao.module.order.controller.admin.delivery.vo.*;
+import cn.iocoder.yudao.module.order.dal.dataobject.ShipmentDetailDO;
+import cn.iocoder.yudao.module.order.dal.dataobject.ShipmentMasterDO;
+import cn.iocoder.yudao.module.order.dal.mysql.ShipmentDetailMapper;
+import cn.iocoder.yudao.module.order.dal.mysql.ShipmentMasterMapper;
+import cn.iocoder.yudao.module.order.service.ShipmentService;
+import com.mzt.logapi.context.LogRecordContext;
+import com.mzt.logapi.service.impl.DiffParseFunction;
+import com.mzt.logapi.starter.annotation.LogRecord;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.order.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.system.enums.LogRecordConstants.*;
+
+/**
+ * 发货单 Service 实现类
+ */
+@Service
+@Validated
+public class ShipmentServiceImpl implements ShipmentService {
+
+    @Resource
+    private ShipmentMasterMapper shipmentMasterMapper;
+
+    @Resource
+    private ShipmentDetailMapper shipmentDetailMapper;
+
+    @Resource
+    private ConfigApi configApi;
+
+    @Override
+    @TenantIgnore
+    public PageResult<ShipmentListItemVO> getShipmentListPage(ShipmentPageReqVO pageReqVO) {
+        return shipmentMasterMapper.selectShipmentListPage(pageReqVO);
+    }
+
+    @Override
+    @TenantIgnore
+    public ShipmentRespVO getShipment(Long id) {
+        ShipmentMasterDO master = shipmentMasterMapper.selectByRecId(id);
+        if (master == null) {
+            return null;
+        }
+
+        ShipmentRespVO resp = convertToRespVO(master);
+        
+        // 查询明细
+        List<ShipmentDetailDO> details = shipmentDetailMapper.selectDetailsByMasterId(master.getId(), master.getDomain());
+        if (CollUtil.isNotEmpty(details)) {
+            resp.setItems(details.stream().map(this::convertDetailToRespVO).collect(Collectors.toList()));
+        }
+        
+        return resp;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @TenantIgnore
+    @LogRecord(type = ORDER_SHIPMENT_TYPE, subType = ORDER_SHIPMENT_CREATE_SUB_TYPE, bizNo = "{{#shipment.id}}",
+            success = ORDER_SHIPMENT_CREATE_SUCCESS)
+    public Long createShipment(ShipmentSaveReqVO createReqVO) {
+        // 获取默认域
+        String domain = configApi.getConfigValueByKey("order.default.domain");
+        if (StrUtil.isBlank(domain)) {
+            domain = "8010";
+        }
+
+        // 创建主表
+        ShipmentMasterDO master = convertToMasterDO(createReqVO);
+        master.setDomain(domain);
+        master.setShType("SH");
+        master.setTyped("N");
+        master.setIsActive(true);
+        master.setIsConfirm(false);
+        master.setCreateTime(LocalDateTime.now());
+        master.setStatus("O"); // 默认状态为Open
+        
+        // 设置必填字段默认值
+        if (StrUtil.isBlank(master.getShipFromId())) {
+            master.setShipFromId(domain); // 默认使用domain作为发货地
+        }
+        if (StrUtil.isBlank(master.getShipToId())) {
+            master.setShipToId(StrUtil.isNotBlank(master.getSoldTo()) ? master.getSoldTo() : domain);
+        }
+        if (StrUtil.isBlank(master.getSoldTo())) {
+            master.setSoldTo("");
+        }
+        
+        // 生成货运单号
+        if (StrUtil.isBlank(master.getId())) {
+            master.setId(generateShipmentId());
+        }
+        
+        shipmentMasterMapper.insert(master);
+
+        // 创建明细
+        if (CollUtil.isNotEmpty(createReqVO.getItems())) {
+            saveDetails(master, createReqVO.getItems());
+        }
+
+        // 记录操作日志上下文
+        LogRecordContext.putVariable("shipment", master);
+        return master.getRecId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @TenantIgnore
+    @LogRecord(type = ORDER_SHIPMENT_TYPE, subType = ORDER_SHIPMENT_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.recId}}",
+            success = ORDER_SHIPMENT_UPDATE_SUCCESS)
+    public void updateShipment(ShipmentSaveReqVO updateReqVO) {
+        // 校验存在
+        ShipmentMasterDO exists = validateShipmentExists(updateReqVO.getRecId());
+        
+        // 校验状态(关闭状态不能编辑)
+        if ("C".equals(exists.getStatus())) {
+            throw exception(SHIPMENT_STATUS_CLOSED);
+        }
+
+        // 更新主表
+        ShipmentMasterDO update = convertToMasterDO(updateReqVO);
+        update.setRecId(exists.getRecId());
+        update.setUpdateTime(LocalDateTime.now());
+        shipmentMasterMapper.updateById(update);
+
+        // 删除旧明细,插入新明细
+        shipmentDetailMapper.deleteByMasterId(exists.getId(), exists.getDomain());
+        if (CollUtil.isNotEmpty(updateReqVO.getItems())) {
+            saveDetails(exists, updateReqVO.getItems());
+        }
+
+        // 记录操作日志上下文
+        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(exists, ShipmentSaveReqVO.class));
+        LogRecordContext.putVariable("shipment", exists);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @TenantIgnore
+    @LogRecord(type = ORDER_SHIPMENT_TYPE, subType = ORDER_SHIPMENT_DELETE_SUB_TYPE, bizNo = "{{#id}}",
+            success = ORDER_SHIPMENT_DELETE_SUCCESS)
+    public void deleteShipment(Long id) {
+        // 校验存在
+        ShipmentMasterDO exists = validateShipmentExists(id);
+        
+        // 校验状态(关闭状态不能删除)
+        if ("C".equals(exists.getStatus())) {
+            throw exception(SHIPMENT_STATUS_CLOSED);
+        }
+
+        // 逻辑删除主表
+        ShipmentMasterDO update = new ShipmentMasterDO();
+        update.setRecId(id);
+        update.setIsActive(false);
+        update.setUpdateTime(LocalDateTime.now());
+        shipmentMasterMapper.updateById(update);
+
+        // 逻辑删除明细
+        shipmentDetailMapper.deleteByMasterId(exists.getId(), exists.getDomain());
+
+        // 记录操作日志上下文
+        LogRecordContext.putVariable("shipment", exists);
+    }
+
+
+    private ShipmentMasterDO validateShipmentExists(Long id) {
+        ShipmentMasterDO master = shipmentMasterMapper.selectByRecId(id);
+        if (master == null) {
+            throw exception(SHIPMENT_NOT_EXISTS);
+        }
+        return master;
+    }
+
+    private void saveDetails(ShipmentMasterDO master, List<ShipmentSaveReqVO.ShipmentDetailSaveReqVO> items) {
+        int line = 1;
+        for (ShipmentSaveReqVO.ShipmentDetailSaveReqVO item : items) {
+            ShipmentDetailDO detail = new ShipmentDetailDO();
+            detail.setDomain(master.getDomain());
+            detail.setId(master.getId());
+            detail.setLine(line++);
+            detail.setOrdNbr(item.getOrdNbr());
+            detail.setOrdLine(item.getOrdLine() != null ? item.getOrdLine() : 0);
+            detail.setContainerItem(item.getContainerItem());
+            detail.setDescr(item.getDescr());
+            detail.setUm(item.getUm());
+            detail.setLocation(item.getLocation());
+            detail.setLotSerial(item.getLotSerial());
+            detail.setQtyToShip(item.getQtyToShip());
+            detail.setPickingQty(item.getPickingQty());
+            detail.setRealQty(item.getRealQty());
+            detail.setStatus(item.getStatus());
+            detail.setRemark(item.getRemark());
+            detail.setShType("SH");
+            detail.setTyped("N");
+            detail.setIsActive(true);
+            detail.setIsConfirm(false);
+            detail.setCreateTime(LocalDateTime.now());
+            detail.setAsnbolShipperRecId(master.getRecId());
+            detail.setShipDate(master.getShipDate());
+            // 设置必填字段默认值
+            detail.setShipFromId(master.getShipFromId() != null ? master.getShipFromId() : master.getDomain());
+            detail.setShipToId(master.getShipToId() != null ? master.getShipToId() : master.getDomain());
+            detail.setDimension1("");
+            detail.setDimension2("");
+            shipmentDetailMapper.insert(detail);
+        }
+    }
+
+    private String generateShipmentId() {
+        // 生成格式:SH + 年月日 + 4位序号
+        String prefix = "SH" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+        // 简单实现,实际应查询数据库获取当日最大序号
+        return prefix + String.format("%04d", System.currentTimeMillis() % 10000);
+    }
+
+    private ShipmentRespVO convertToRespVO(ShipmentMasterDO master) {
+        ShipmentRespVO resp = new ShipmentRespVO();
+        resp.setRecId(master.getRecId());
+        resp.setId(master.getId());
+        resp.setOrdNbr(master.getOrdNbr());
+        resp.setShipDate(master.getShipDate());
+        resp.setShipTime(master.getShipTime());
+        resp.setStatus(master.getStatus());
+        resp.setSite(master.getSite());
+        resp.setDepartment(master.getDepartment());
+        resp.setRemark(master.getRemark());
+        resp.setLotSerial(master.getLotSerial());
+        resp.setLocation(master.getLocation());
+        resp.setGrossWeight(master.getGrossWeight());
+        resp.setNetWeight(master.getNetWeight());
+        resp.setVolume(master.getVolume());
+        resp.setQtyToShip(master.getQtyToShip());
+        resp.setCustPo(master.getCustPo());
+        resp.setSoldTo(master.getSoldTo());
+        return resp;
+    }
+
+    private ShipmentRespVO.ShipmentDetailRespVO convertDetailToRespVO(ShipmentDetailDO detail) {
+        ShipmentRespVO.ShipmentDetailRespVO resp = new ShipmentRespVO.ShipmentDetailRespVO();
+        resp.setRecId(detail.getRecId());
+        resp.setLine(detail.getLine());
+        resp.setOrdNbr(detail.getOrdNbr());
+        resp.setOrdLine(detail.getOrdLine());
+        resp.setContainerItem(detail.getContainerItem());
+        resp.setDescr(detail.getDescr());
+        resp.setUm(detail.getUm());
+        resp.setLocation(detail.getLocation());
+        resp.setLotSerial(detail.getLotSerial());
+        resp.setQtyToShip(detail.getQtyToShip());
+        resp.setPickingQty(detail.getPickingQty());
+        resp.setRealQty(detail.getRealQty());
+        resp.setStatus(detail.getStatus());
+        resp.setRemark(detail.getRemark());
+        return resp;
+    }
+
+    private ShipmentMasterDO convertToMasterDO(ShipmentSaveReqVO reqVO) {
+        ShipmentMasterDO master = new ShipmentMasterDO();
+        master.setId(reqVO.getId());
+        master.setOrdNbr(reqVO.getOrdNbr());
+        if (StrUtil.isNotBlank(reqVO.getShipDate())) {
+            master.setShipDate(LocalDateTime.parse(reqVO.getShipDate() + " 00:00:00", 
+                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+        }
+        master.setShipTime(reqVO.getShipTime());
+        master.setStatus(reqVO.getStatus());
+        master.setSite(reqVO.getSite());
+        master.setDepartment(reqVO.getDepartment());
+        master.setRemark(reqVO.getRemark());
+        master.setLotSerial(reqVO.getLotSerial());
+        master.setLocation(reqVO.getLocation());
+        master.setGrossWeight(reqVO.getGrossWeight());
+        master.setNetWeight(reqVO.getNetWeight());
+        master.setVolume(reqVO.getVolume());
+        master.setQtyToShip(reqVO.getQtyToShip());
+        master.setCustPo(reqVO.getCustPo());
+        master.setSoldTo(reqVO.getSoldTo());
+        return master;
+    }
+
+    @Override
+    @TenantIgnore
+    public List<Map<String, Object>> getSalesOrderOptions() {
+        return shipmentMasterMapper.selectSalesOrderOptions();
+    }
+
+    @Override
+    @TenantIgnore
+    public List<Map<String, Object>> getCustomerOptions() {
+        return shipmentMasterMapper.selectCustomerOptions();
+    }
+
+    @Override
+    @TenantIgnore
+    public List<Map<String, Object>> getDepartmentOptions() {
+        return shipmentMasterMapper.selectDepartmentOptions();
+    }
+}

+ 66 - 0
yudao-order-server/src/main/resources/mapper/order/ShipmentDetailMapper.xml

@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.order.dal.mysql.ShipmentDetailMapper">
+
+    <!-- 根据主表ID查询明细列表 -->
+    <select id="selectDetailsByMasterId" resultType="cn.iocoder.yudao.module.order.dal.dataobject.ShipmentDetailDO">
+        SELECT 
+            RecID as recId,
+            Domain as domain,
+            Id as id,
+            Line as line,
+            OrdNbr as ordNbr,
+            OrdLine as ordLine,
+            ContainerItem as containerItem,
+            Descr as descr,
+            UM as um,
+            Location as location,
+            LotSerial as lotSerial,
+            QtyToShip as qtyToShip,
+            PickingQty as pickingQty,
+            RealQty as realQty,
+            Status as status,
+            Remark as remark,
+            ShType as shType,
+            Typed as typed,
+            IsActive as isActive,
+            IsConfirm as isConfirm,
+            CreateUser as createUser,
+            CreateTime as createTime,
+            UpdateUser as updateUser,
+            UpdateTime as updateTime,
+            ASNBOLShipperRecID as asnbolShipperRecId,
+            ShipDate as shipDate,
+            CustPO as custPo,
+            Price as price,
+            GrossWeight as grossWeight,
+            NetWeight as netWeight,
+            Volume as volume
+        FROM ASNBOLShipperDetail
+        WHERE Id = #{id} AND Domain = #{domain} AND IsActive = 1
+        ORDER BY Line
+    </select>
+
+    <!-- 根据主表ID删除明细 -->
+    <update id="deleteByMasterId">
+        UPDATE ASNBOLShipperDetail 
+        SET IsActive = 0, UpdateTime = NOW()
+        WHERE Id = #{id} AND Domain = #{domain}
+    </update>
+
+    <!-- 插入发货单明细 -->
+    <insert id="insert" useGeneratedKeys="true" keyProperty="recId" keyColumn="RecID">
+        INSERT INTO ASNBOLShipperDetail (
+            Domain, Id, Line, OrdNbr, OrdLine, ContainerItem, Descr, UM,
+            Location, LotSerial, QtyToShip, PickingQty, RealQty, Status, Remark,
+            ShType, Typed, IsActive, IsConfirm, CreateTime, ASNBOLShipperRecID, ShipDate,
+            ShipFromId, ShipToId, Dimension1, Dimension2
+        ) VALUES (
+            #{domain}, #{id}, #{line}, #{ordNbr}, #{ordLine}, #{containerItem}, #{descr}, #{um},
+            #{location}, #{lotSerial}, #{qtyToShip}, #{pickingQty}, #{realQty}, #{status}, #{remark},
+            #{shType}, #{typed}, #{isActive}, #{isConfirm}, #{createTime}, #{asnbolShipperRecId}, #{shipDate},
+            #{shipFromId}, #{shipToId}, #{dimension1}, #{dimension2}
+        )
+    </insert>
+
+</mapper>

+ 149 - 0
yudao-order-server/src/main/resources/mapper/order/ShipmentMasterMapper.xml

@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.order.dal.mysql.ShipmentMasterMapper">
+
+    <!-- 根据RecID查询发货单 -->
+    <select id="selectByRecId" resultType="cn.iocoder.yudao.module.order.dal.dataobject.ShipmentMasterDO">
+        SELECT 
+            RecID as recId,
+            Domain as domain,
+            Id as id,
+            OrdNbr as ordNbr,
+            ShipDate as shipDate,
+            ShipTime as shipTime,
+            Status as status,
+            Site as site,
+            Department as department,
+            Remark as remark,
+            ShType as shType,
+            Typed as typed,
+            IsActive as isActive,
+            IsConfirm as isConfirm,
+            CreateUser as createUser,
+            CreateTime as createTime,
+            UpdateUser as updateUser,
+            UpdateTime as updateTime,
+            LotSerial as lotSerial,
+            Location as location,
+            GrossWeight as grossWeight,
+            NetWeight as netWeight,
+            Volume as volume,
+            WeightUM as weightUm,
+            VolumeUM as volumeUm,
+            QtyToShip as qtyToShip,
+            QtyShipped as qtyShipped,
+            CustPO as custPo,
+            SoldTo as soldTo,
+            ShipFromId as shipFromId,
+            ShipToId as shipToId,
+            Descr as descr,
+            Curr as curr,
+            Price as price
+        FROM ASNBOLShipperMaster
+        WHERE RecID = #{recId}
+    </select>
+
+    <!-- 发货单列表查询 -->
+    <select id="selectShipmentList" resultType="cn.iocoder.yudao.module.order.controller.admin.delivery.vo.ShipmentListItemVO">
+        SELECT 
+            a.RecID as id,
+            a.Id as id1,
+            a.OrdNbr as ordNbr,
+            CONCAT(b.Department, ' ', IFNULL(b.Descr, '')) AS departmentName,
+            a.ShipDate as shipDate,
+            a.Status as status,
+            a.Remark as remark
+        FROM ASNBOLShipperMaster a
+        LEFT JOIN DepartmentMaster b ON a.Department = b.Department AND a.Domain = b.Domain
+        WHERE a.ShType = 'SH' AND a.Typed &lt;&gt; 'S' AND a.IsActive = 1
+        <if test="req.id != null and req.id != ''">
+            AND a.Id LIKE CONCAT('%', #{req.id}, '%')
+        </if>
+        <if test="req.ordNbr != null and req.ordNbr != ''">
+            AND a.OrdNbr LIKE CONCAT('%', #{req.ordNbr}, '%')
+        </if>
+        <if test="req.status != null and req.status != ''">
+            AND a.Status = #{req.status}
+        </if>
+        <if test="req.department != null and req.department != ''">
+            AND a.Department LIKE CONCAT('%', #{req.department}, '%')
+        </if>
+        <if test="req.shipDateStart != null and req.shipDateStart != ''">
+            AND DATE(a.ShipDate) &gt;= #{req.shipDateStart}
+        </if>
+        <if test="req.shipDateEnd != null and req.shipDateEnd != ''">
+            AND DATE(a.ShipDate) &lt;= #{req.shipDateEnd}
+        </if>
+        ORDER BY a.CreateTime DESC
+    </select>
+
+    <!-- 获取销售单号下拉列表 -->
+    <select id="selectSalesOrderOptions" resultType="java.util.Map">
+        SELECT 
+            s.SalesOrd as value,
+            CONCAT(s.SalesOrd, '_', s.SoldTo, '_', IFNULL(c.SortName, '')) as label
+        FROM SalesOrdMaster s
+        LEFT JOIN CustMaster c ON s.SoldTo = c.Cust
+        WHERE s.IsActive = 1
+        ORDER BY s.SalesOrd DESC
+    </select>
+
+    <!-- 获取客户下拉列表 -->
+    <select id="selectCustomerOptions" resultType="java.util.Map">
+        SELECT 
+            Cust as value,
+            CONCAT(RTRIM(Cust), ' ', IFNULL(SortName, '')) as label
+        FROM CustMaster
+        ORDER BY Cust
+    </select>
+
+    <!-- 获取部门下拉列表 -->
+    <select id="selectDepartmentOptions" resultType="java.util.Map">
+        SELECT 
+            Department as value,
+            CONCAT(RTRIM(Department), ' ', IFNULL(Descr, '')) as label
+        FROM DepartmentMaster
+        ORDER BY Department
+    </select>
+
+    <!-- 插入发货单主表 -->
+    <insert id="insert" useGeneratedKeys="true" keyProperty="recId" keyColumn="RecID">
+        INSERT INTO ASNBOLShipperMaster (
+            Domain, Id, OrdNbr, ShipDate, ShipTime, Status, Site, Department, 
+            Remark, ShType, Typed, IsActive, IsConfirm, CreateTime, 
+            LotSerial, Location, GrossWeight, NetWeight, Volume, 
+            QtyToShip, CustPO, SoldTo, ShipFromId, ShipToId
+        ) VALUES (
+            #{domain}, #{id}, #{ordNbr}, #{shipDate}, #{shipTime}, #{status}, #{site}, #{department},
+            #{remark}, #{shType}, #{typed}, #{isActive}, #{isConfirm}, #{createTime},
+            #{lotSerial}, #{location}, #{grossWeight}, #{netWeight}, #{volume},
+            #{qtyToShip}, #{custPo}, #{soldTo}, #{shipFromId}, #{shipToId}
+        )
+    </insert>
+
+    <!-- 更新发货单主表 -->
+    <update id="updateById">
+        UPDATE ASNBOLShipperMaster
+        <set>
+            <if test="ordNbr != null">OrdNbr = #{ordNbr},</if>
+            <if test="shipDate != null">ShipDate = #{shipDate},</if>
+            <if test="shipTime != null">ShipTime = #{shipTime},</if>
+            <if test="status != null">Status = #{status},</if>
+            <if test="site != null">Site = #{site},</if>
+            <if test="department != null">Department = #{department},</if>
+            <if test="remark != null">Remark = #{remark},</if>
+            <if test="isActive != null">IsActive = #{isActive},</if>
+            <if test="updateTime != null">UpdateTime = #{updateTime},</if>
+            <if test="lotSerial != null">LotSerial = #{lotSerial},</if>
+            <if test="location != null">Location = #{location},</if>
+            <if test="grossWeight != null">GrossWeight = #{grossWeight},</if>
+            <if test="netWeight != null">NetWeight = #{netWeight},</if>
+            <if test="volume != null">Volume = #{volume},</if>
+            <if test="qtyToShip != null">QtyToShip = #{qtyToShip},</if>
+            <if test="custPo != null">CustPO = #{custPo},</if>
+            <if test="soldTo != null">SoldTo = #{soldTo},</if>
+        </set>
+        WHERE RecID = #{recId}
+    </update>
+
+</mapper>

+ 3 - 0
yudao-order-server/src/main/resources/mapper/order/ShippingPlanMapper.xml

@@ -39,12 +39,14 @@
             b.OrdNbr,
             b.bill_no,
             b.OrdDate,
+            d.entry_seq as ordLine,
             b.Country,
             b.CustomNo,
             b.CustomName,
             b.ItemNum,
             b.ItemName,
             b.Specification,
+            d.unit as um,
             b.Qty,
             b.Packaging,
             c.RecID,
@@ -53,6 +55,7 @@
             b.Weight
         FROM ShippingPlan a
         LEFT JOIN ShippingPlanDetail b ON a.recid = b.plan_id
+        LEFT JOIN crm_seorderentry d ON b.sentry_id = d.Id
         LEFT JOIN ASNBOLShipperDetail c ON b.itemnum = c.ContainerItem 
             AND b.bill_no = c.ordnbr 
             AND c.shtype = 'SH' 

+ 15 - 0
yudao-ui/yudao-ui-admin-vue3/src/api/jiaohuo/order.ts

@@ -106,3 +106,18 @@ export const createShippingPlan = (data: any) => {
 export const updateShippingPlan = (data: any) => {
   return request.put({ url: '/order/shipping-plan', data })
 }
+
+// 获取销售单号下拉列表
+export const getSalesOrderOptions = () => {
+  return request.get({ url: '/order/shipment/sales-orders' })
+}
+
+// 获取客户下拉列表
+export const getCustomerOptions = () => {
+  return request.get({ url: '/order/shipment/customers' })
+}
+
+// 获取部门下拉列表
+export const getDepartmentOptions = () => {
+  return request.get({ url: '/order/shipment/departments' })
+}

+ 51 - 47
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/OrderShipment.vue

@@ -5,7 +5,7 @@
       <el-form :inline="true" :model="searchForm" class="search-form">
         <el-form-item label="发货单号">
           <el-input
-            v-model="searchForm.Id1"
+            v-model="searchForm.id"
             placeholder="请输入发货单号"
             clearable
             @keyup.enter="handleSearch"
@@ -13,7 +13,7 @@
         </el-form-item>
         <el-form-item label="销售单号">
           <el-input
-            v-model="searchForm.OrdNbr"
+            v-model="searchForm.ordNbr"
             placeholder="请输入销售单号"
             clearable
             @keyup.enter="handleSearch"
@@ -21,7 +21,7 @@
         </el-form-item>
         <el-form-item label="部门">
           <el-input
-            v-model="searchForm.Departmentname"
+            v-model="searchForm.department"
             placeholder="请输入部门"
             clearable
             @keyup.enter="handleSearch"
@@ -29,11 +29,21 @@
         </el-form-item>
         <el-form-item label="发货日期">
           <el-date-picker
-            v-model="searchForm.ShipDate"
+            v-model="searchForm.shipDateStart"
             type="date"
-            placeholder="请选择日期"
+            placeholder="开始日期"
             value-format="YYYY-MM-DD"
             clearable
+            style="width: 140px"
+          />
+          <span style="margin: 0 5px">-</span>
+          <el-date-picker
+            v-model="searchForm.shipDateEnd"
+            type="date"
+            placeholder="结束日期"
+            value-format="YYYY-MM-DD"
+            clearable
+            style="width: 140px"
           />
         </el-form-item>
         <el-form-item>
@@ -51,35 +61,31 @@
       </div>
 
       <!-- 表格 -->
-      <el-table
-        v-loading="loading"
-        :data="tableData"
-        border
-        stripe
-      >
-        <el-table-column prop="Id1" label="发货单号" width="180" fixed="left" />
-        <el-table-column prop="OrdNbr" label="销售单号" width="180" />
-        <el-table-column prop="Departmentname" label="部门" width="200" show-overflow-tooltip />
-        <el-table-column prop="ShipDate" label="发货日期" width="120">
+      <el-table v-loading="loading" :data="tableData" border stripe>
+        <el-table-column prop="id1" label="发货单号" width="180" fixed="left" />
+        <el-table-column prop="ordNbr" label="销售单号" width="180" />
+        <el-table-column prop="departmentName" label="部门" width="200" show-overflow-tooltip />
+        <el-table-column prop="shipDate" label="发货日期" width="120">
           <template #default="{ row }">
-            {{ row.ShipDate ? row.ShipDate.substring(0, 10) : '' }}
+            {{ row.shipDate ? String(row.shipDate).substring(0, 10) : '' }}
           </template>
         </el-table-column>
-        <el-table-column prop="Status" label="状态" width="100">
+        <el-table-column prop="status" label="状态" width="100">
           <template #default="{ row }">
-            <el-tag v-if="row.Status === 'C'" type="success">关闭</el-tag>
-            <el-tag v-else type="info">{{ row.Status }}</el-tag>
+            <el-tag v-if="row.status === 'C'" type="success">关闭</el-tag>
+            <el-tag v-else-if="row.status === 'O'" type="info">打开</el-tag>
+            <el-tag v-else type="info">{{ row.status }}</el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="Remark" label="备注" min-width="200" show-overflow-tooltip />
+        <el-table-column prop="remark" label="备注" min-width="200" show-overflow-tooltip />
         <el-table-column label="操作" width="200" fixed="right">
           <template #default="{ row }">
             <el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
-            <el-button 
-              v-if="row.Status !== 'C'" 
-              type="danger" 
-              link 
-              :icon="Delete" 
+            <el-button
+              v-if="row.status !== 'C'"
+              type="danger"
+              link
+              :icon="Delete"
               @click="handleDelete(row)"
             >
               删除
@@ -91,7 +97,7 @@
 
       <!-- 分页 -->
       <el-pagination
-        v-model:current-page="pagination.page"
+        v-model:current-page="pagination.pageNo"
         v-model:page-size="pagination.pageSize"
         :page-sizes="[20, 50, 100]"
         :total="pagination.total"
@@ -108,7 +114,7 @@
 </template>
 
 <script setup>
-import { ref, reactive } from 'vue'
+import { ref, reactive, onMounted } from 'vue'
 import { Search, Refresh, Plus, Edit, Delete, View } from '@element-plus/icons-vue'
 import { getShipmentList, deleteShipment } from '@/api/jiaohuo/order'
 import { ElMessage, ElMessageBox } from 'element-plus'
@@ -118,14 +124,15 @@ const loading = ref(false)
 const tableData = ref([])
 
 const searchForm = reactive({
-  Id1: '',
-  OrdNbr: '',
-  Departmentname: '',
-  ShipDate: ''
+  id: '',
+  ordNbr: '',
+  department: '',
+  shipDateStart: '',
+  shipDateEnd: ''
 })
 
 const pagination = reactive({
-  page: 1,
+  pageNo: 1,
   pageSize: 20,
   total: 0
 })
@@ -138,19 +145,15 @@ const handleSearch = async () => {
   try {
     const params = {
       ...searchForm,
-      page: pagination.page,
+      pageNo: pagination.pageNo,
       pageSize: pagination.pageSize
     }
-    const { data } = await getShipmentList(params)
-    tableData.value = data.list || []
-    pagination.total = data.total || 0
+    const res = await getShipmentList(params)
+    tableData.value = res?.list || []
+    pagination.total = res?.total || 0
   } catch (error) {
     console.error('查询失败:', error)
-    if (error.code === 'ERR_NETWORK' || error.response?.status === 404) {
-      ElMessage.warning('后端接口暂未实现,请先配置后端服务')
-    } else {
-      ElMessage.error('查询失败:' + (error.message || '未知错误'))
-    }
+    ElMessage.error('查询失败:' + (error.message || '未知错误'))
     tableData.value = []
     pagination.total = 0
   } finally {
@@ -160,10 +163,10 @@ const handleSearch = async () => {
 
 // 重置
 const handleReset = () => {
-  Object.keys(searchForm).forEach(key => {
+  Object.keys(searchForm).forEach((key) => {
     searchForm[key] = ''
   })
-  pagination.page = 1
+  pagination.pageNo = 1
   handleSearch()
 }
 
@@ -179,7 +182,7 @@ const handleEdit = (row) => {
 
 // 删除
 const handleDelete = async (row) => {
-  if (row.Status === 'C') {
+  if (row.status === 'C') {
     ElMessage.warning('状态为关闭的记录不允许删除')
     return
   }
@@ -207,8 +210,10 @@ const handleView = (row) => {
   shipmentFormRef.value.open('view', row.id)
 }
 
-// 初始化 - 注释掉自动查询,等后端API准备好后再启用
-// handleSearch()
+// 初始化
+onMounted(() => {
+  handleSearch()
+})
 </script>
 
 <style scoped lang="scss">
@@ -231,4 +236,3 @@ const handleView = (row) => {
   }
 }
 </style>
-

+ 10 - 26
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/ShippingPlan.vue

@@ -131,15 +131,19 @@
 
     <!-- 出货计划表单 -->
     <ShippingPlanForm ref="planFormRef" @success="handleSearch" />
+    
+    <!-- 发货表单 -->
+    <ShipmentForm ref="shipmentFormRef" @success="handleSearch" />
   </div>
 </template>
 
 <script setup>
 import { ref, reactive, computed } from 'vue'
 import { Search, Refresh, Plus, Box, Edit, View } from '@element-plus/icons-vue'
-import { getShippingPlanList, salesOutbound } from '@/api/jiaohuo/order'
-import { ElMessage, ElMessageBox } from 'element-plus'
+import { getShippingPlanList } from '@/api/jiaohuo/order'
+import { ElMessage } from 'element-plus'
 import ShippingPlanForm from './components/ShippingPlanForm.vue'
+import ShipmentForm from './components/ShipmentForm.vue'
 
 const loading = ref(false)
 const tableData = ref([])
@@ -165,6 +169,7 @@ const hasValidSelection = computed(() => {
 })
 
 const planFormRef = ref(null)
+const shipmentFormRef = ref(null)
 
 // 查询
 const handleSearch = async () => {
@@ -227,8 +232,7 @@ const handleView = (row) => {
 }
 
 // 销售出库
-// 销售出库
-const handleSalesOutbound = async () => {
+const handleSalesOutbound = () => {
   if (!selectedRows.value.length) {
     ElMessage.warning('请先选择出货计划')
     return
@@ -240,28 +244,8 @@ const handleSalesOutbound = async () => {
     return
   }
 
-  try {
-    await ElMessageBox.confirm('确定要对选中的记录进行销售出库吗?', '提示', {
-      type: 'warning'
-    })
-
-    loading.value = true
-    const ids = selectedRows.value.map(row => row.id).join(',')
-    // 这里需要传入组织编号和操作人账号,从用户状态中获取
-    await salesOutbound({
-      ids,
-      organization: '', // 从用户状态获取
-      operator: '' // 从用户状态获取
-    })
-    ElMessage.success('销售出库成功')
-    handleSearch()
-  } catch (error) {
-    if (error !== 'cancel') {
-      ElMessage.error('销售出库失败:' + (error.message || '未知错误'))
-    }
-  } finally {
-    loading.value = false
-  }
+  // 弹出发货表单,传入选中的出货计划数据
+  shipmentFormRef.value.open('add', selectedRows.value)
 }
 
 // 初始化 - 页面加载时自动查询

+ 260 - 146
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/components/ShipmentForm.vue

@@ -10,47 +10,86 @@
       ref="formRef"
       :model="formData"
       :rules="formRules"
-      label-width="120px"
+      label-width="100px"
       :disabled="mode === 'view'"
     >
       <el-row :gutter="20">
         <el-col :span="8">
-          <el-form-item label="出货编号" prop="lotserial">
-            <el-input v-model="formData.lotserial" placeholder="请输入出货编号" />
+          <el-form-item label="发货单号" prop="id">
+            <el-input v-model="formData.id" placeholder="请输入发货单号(留空自动生成)" />
           </el-form-item>
         </el-col>
         <el-col :span="8">
-          <el-form-item label="出货日期" prop="shippingdate">
-            <el-date-picker
-              v-model="formData.shippingdate"
-              type="date"
-              placeholder="请选择出货日期"
-              value-format="YYYY-MM-DD"
+          <el-form-item label="销售单号" prop="ordNbr">
+            <el-select
+              v-model="formData.ordNbr"
+              filterable
+              clearable
+              placeholder="请选择销售单号"
               style="width: 100%"
-            />
+            >
+              <el-option
+                v-for="item in salesOrderOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
           </el-form-item>
         </el-col>
         <el-col :span="8">
-          <el-form-item label="出货地点" prop="shippingsite">
-            <el-input v-model="formData.shippingsite" placeholder="请输入出货地点" />
+          <el-form-item label="客户" prop="soldTo">
+            <el-select
+              v-model="formData.soldTo"
+              filterable
+              clearable
+              placeholder="请选择客户"
+              style="width: 100%"
+            >
+              <el-option
+                v-for="item in customerOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
           </el-form-item>
         </el-col>
       </el-row>
 
       <el-row :gutter="20">
         <el-col :span="8">
-          <el-form-item label="收货人" prop="consignee">
-            <el-input v-model="formData.consignee" placeholder="请输入收货人" />
+          <el-form-item label="发货日期" prop="shipDate">
+            <el-date-picker
+              v-model="formData.shipDate"
+              type="date"
+              placeholder="请选择发货日期"
+              value-format="x"
+              style="width: 100%"
+            />
           </el-form-item>
         </el-col>
         <el-col :span="8">
-          <el-form-item label="联系方式" prop="telephone">
-            <el-input v-model="formData.telephone" placeholder="请输入联系方式" />
+          <el-form-item label="部门" prop="department">
+            <el-select
+              v-model="formData.department"
+              filterable
+              clearable
+              placeholder="请选择部门"
+              style="width: 100%"
+            >
+              <el-option
+                v-for="item in departmentOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
           </el-form-item>
         </el-col>
         <el-col :span="8">
-          <el-form-item label="收货地址" prop="shippingaddress">
-            <el-input v-model="formData.shippingaddress" placeholder="请输入收货地址" />
+          <el-form-item label="状态" prop="status">
+            <el-input v-model="formData.status" placeholder="请输入状态" />
           </el-form-item>
         </el-col>
       </el-row>
@@ -61,7 +100,7 @@
             <el-input
               v-model="formData.remark"
               type="textarea"
-              :rows="3"
+              :rows="2"
               placeholder="请输入备注"
             />
           </el-form-item>
@@ -71,11 +110,11 @@
       <!-- 子表 -->
       <el-divider content-position="left">发货明细</el-divider>
       <div class="table-toolbar">
-        <el-button 
-          v-if="mode !== 'view'" 
-          type="primary" 
-          :icon="Plus" 
-          size="small" 
+        <el-button
+          v-if="mode !== 'view'"
+          type="primary"
+          :icon="Plus"
+          size="small"
           @click="handleAddRow"
         >
           添加
@@ -83,105 +122,73 @@
       </div>
 
       <el-table :data="formData.items" border stripe max-height="400">
-        <el-table-column prop="Line" label="项次" width="80">
-          <template #default="{ row }">
-            <el-input v-if="mode !== 'view'" v-model="row.Line" />
-            <span v-else>{{ row.Line }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="OrdNbr" label="订单号" width="150">
-          <template #default="{ row }">
-            <el-input v-if="mode !== 'view'" v-model="row.OrdNbr" />
-            <span v-else>{{ row.OrdNbr }}</span>
-          </template>
+        <el-table-column prop="line" label="项次" width="70" align="center">
+          <template #default="{ $index }">{{ $index + 1 }}</template>
         </el-table-column>
-        <el-table-column prop="OrdLine" label="订单项次" width="100">
+        <el-table-column prop="ordNbr" label="订单号" width="140">
           <template #default="{ row }">
-            <el-input v-if="mode !== 'view'" v-model="row.OrdLine" />
-            <span v-else>{{ row.OrdLine }}</span>
+            <el-input v-if="mode !== 'view'" v-model="row.ordNbr" size="small" />
+            <span v-else>{{ row.ordNbr }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="ContainerItem" label="集装箱零件" width="150">
+        <el-table-column prop="ordLine" label="订单项次" width="90">
           <template #default="{ row }">
-            <el-input v-if="mode !== 'view'" v-model="row.ContainerItem" />
-            <span v-else>{{ row.ContainerItem }}</span>
+            <el-input v-if="mode !== 'view'" v-model="row.ordLine" size="small" />
+            <span v-else>{{ row.ordLine }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="Descr" label="描述" width="180">
+        <el-table-column prop="containerItem" label="物料编号" width="140">
           <template #default="{ row }">
-            <el-input v-if="mode !== 'view'" v-model="row.Descr" />
-            <span v-else>{{ row.Descr }}</span>
+            <el-input v-if="mode !== 'view'" v-model="row.containerItem" size="small" />
+            <span v-else>{{ row.containerItem }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="UM" label="单位" width="80">
+        <el-table-column prop="descr" label="物料名称" width="160">
           <template #default="{ row }">
-            <el-input v-if="mode !== 'view'" v-model="row.UM" />
-            <span v-else>{{ row.UM }}</span>
+            <el-input v-if="mode !== 'view'" v-model="row.descr" size="small" />
+            <span v-else>{{ row.descr }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="Location" label="库位" width="120">
+        <el-table-column prop="um" label="单位" width="70">
           <template #default="{ row }">
-            <el-input v-if="mode !== 'view'" v-model="row.Location" />
-            <span v-else>{{ row.Location }}</span>
+            <el-input v-if="mode !== 'view'" v-model="row.um" size="small" />
+            <span v-else>{{ row.um }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="LotSerial" label="批次号" width="150">
-          <template #default="{ row }">
-            <el-input v-if="mode !== 'view'" v-model="row.LotSerial" />
-            <span v-else>{{ row.LotSerial }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="QtyToShip" label="发货数量" width="120">
+        <el-table-column prop="qtyToShip" label="发货数量" width="100">
           <template #default="{ row }">
             <el-input-number
               v-if="mode !== 'view'"
-              v-model="row.QtyToShip"
+              v-model="row.qtyToShip"
               :min="0"
               :controls="false"
+              size="small"
               style="width: 100%"
             />
-            <span v-else>{{ row.QtyToShip }}</span>
+            <span v-else>{{ row.qtyToShip }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="PickingQty" label="已备数" width="100">
+        <el-table-column prop="location" label="库位" width="100">
           <template #default="{ row }">
-            <el-input-number
-              v-if="mode !== 'view'"
-              v-model="row.PickingQty"
-              :min="0"
-              :controls="false"
-              style="width: 100%"
-            />
-            <span v-else>{{ row.PickingQty }}</span>
+            <el-input v-if="mode !== 'view'" v-model="row.location" size="small" />
+            <span v-else>{{ row.location }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="RealQty" label="已发数" width="100">
+        <el-table-column prop="lotSerial" label="批次号" width="120">
           <template #default="{ row }">
-            <el-input-number
-              v-if="mode !== 'view'"
-              v-model="row.RealQty"
-              :min="0"
-              :controls="false"
-              style="width: 100%"
-            />
-            <span v-else>{{ row.RealQty }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="Status" label="状态" width="100">
-          <template #default="{ row }">
-            <el-input v-if="mode !== 'view'" v-model="row.Status" />
-            <span v-else>{{ row.Status }}</span>
+            <el-input v-if="mode !== 'view'" v-model="row.lotSerial" size="small" />
+            <span v-else>{{ row.lotSerial }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="Remark" label="备注" min-width="150">
+        <el-table-column prop="remark" label="备注" min-width="120">
           <template #default="{ row }">
-            <el-input v-if="mode !== 'view'" v-model="row.Remark" type="textarea" :rows="1" />
-            <span v-else>{{ row.Remark }}</span>
+            <el-input v-if="mode !== 'view'" v-model="row.remark" size="small" />
+            <span v-else>{{ row.remark }}</span>
           </template>
         </el-table-column>
-        <el-table-column v-if="mode !== 'view'" label="操作" width="100" fixed="right">
+        <el-table-column v-if="mode !== 'view'" label="操作" width="80" fixed="right" align="center">
           <template #default="{ $index }">
-            <el-button type="danger" link :icon="Delete" @click="handleDeleteRow($index)">删除</el-button>
+            <el-button type="danger" link :icon="Delete" @click="handleDeleteRow($index)" />
           </template>
         </el-table-column>
       </el-table>
@@ -200,64 +207,175 @@
 </template>
 
 <script setup>
-import { ref, reactive, computed } from 'vue'
+import { ref, reactive, computed, nextTick } from 'vue'
 import { Plus, Delete } from '@element-plus/icons-vue'
-import { getShipmentDetail, createShipment, updateShipment } from '@/api/jiaohuo/order'
+import {
+  getShipmentDetail,
+  createShipment,
+  updateShipment,
+  getSalesOrderOptions,
+  getCustomerOptions,
+  getDepartmentOptions
+} from '@/api/jiaohuo/order'
 import { ElMessage } from 'element-plus'
 
 const dialogVisible = ref(false)
 const submitLoading = ref(false)
-const mode = ref('add') // add, edit, view
+const mode = ref('add')
 const currentId = ref(null)
 
 const formRef = ref(null)
 
+// 下拉列表数据
+const salesOrderOptions = ref([])
+const customerOptions = ref([])
+const departmentOptions = ref([])
+
 const formData = reactive({
-  lotserial: '',
-  shippingdate: '',
-  shippingsite: '',
-  consignee: '',
-  telephone: '',
-  shippingaddress: '',
+  id: '',
+  ordNbr: '',
+  soldTo: '',
+  shipDate: '',
+  department: '',
+  status: '',
   remark: '',
   items: []
 })
 
 const formRules = {
-  lotserial: [{ required: true, message: '请输入出货编号', trigger: 'blur' }],
-  shippingdate: [{ required: true, message: '请选择出货日期', trigger: 'change' }],
-  shippingsite: [{ required: true, message: '请输入出货地点', trigger: 'blur' }]
+  shipDate: [{ required: true, message: '请选择发货日期', trigger: 'change' }]
 }
 
 const dialogTitle = computed(() => {
-  const titleMap = {
-    add: '添加发货单',
-    edit: '编辑发货单',
-    view: '查看发货单'
-  }
+  const titleMap = { add: '添加发货单', edit: '编辑发货单', view: '查看发货单' }
   return titleMap[mode.value]
 })
 
 const emit = defineEmits(['success'])
 
+// 加载下拉列表数据
+const loadOptions = async () => {
+  try {
+    const [salesRes, custRes, deptRes] = await Promise.all([
+      getSalesOrderOptions(),
+      getCustomerOptions(),
+      getDepartmentOptions()
+    ])
+    salesOrderOptions.value = salesRes || []
+    customerOptions.value = custRes || []
+    departmentOptions.value = deptRes || []
+  } catch (error) {
+    console.error('加载下拉列表失败', error)
+  }
+}
+
+// 获取当前日期字符串
+const getCurrentDate = () => {
+  const now = new Date()
+  const year = now.getFullYear()
+  const month = String(now.getMonth() + 1).padStart(2, '0')
+  const day = String(now.getDate()).padStart(2, '0')
+  return `${year}-${month}-${day}`
+}
+
+// 获取默认表单数据
+const getDefaultFormData = () => ({
+  id: '',
+  ordNbr: '',
+  soldTo: '',
+  shipDate: getCurrentDate(), // 默认当前日期
+  department: '',
+  status: '',
+  remark: '',
+  items: []
+})
+
 // 打开对话框
-const open = async (type, id) => {
+const open = async (type, dataOrId) => {
   mode.value = type
-  currentId.value = id
+  currentId.value = null
+
+  if (type === 'add') {
+    // 重置表单并设置默认日期(时间戳格式)
+    formData.id = ''
+    formData.ordNbr = ''
+    formData.soldTo = ''
+    formData.shipDate = Date.now()
+    formData.department = ''
+    formData.status = ''
+    formData.remark = ''
+    formData.items = []
+  }
+
   dialogVisible.value = true
 
+  // 异步加载下拉列表
+  loadOptions()
+
   if (type === 'add') {
-    resetForm()
-  } else if (id) {
-    await loadData(id)
+    if (Array.isArray(dataOrId) && dataOrId.length > 0) {
+      loadShippingPlanData(dataOrId)
+    }
+  } else if (dataOrId) {
+    currentId.value = dataOrId
+    await loadData(dataOrId)
+  }
+}
+
+// 加载出货计划数据
+const loadShippingPlanData = (plans) => {
+  try {
+    const firstPlan = plans[0]
+    formData.ordNbr = firstPlan.billNo || ''
+    formData.soldTo = firstPlan.customNo || ''
+    // 如果出货计划有有效日期则转换为时间戳,否则使用当前日期
+    if (firstPlan.shippingDate) {
+      const dateStr = String(firstPlan.shippingDate).substring(0, 10)
+      const timestamp = new Date(dateStr).getTime()
+      // 确保日期有效
+      formData.shipDate = isNaN(timestamp) ? Date.now() : timestamp
+    } else {
+      formData.shipDate = Date.now()
+    }
+
+    formData.items = plans.map((plan) => ({
+      ordNbr: plan.billNo || '',
+      ordLine: plan.ordLine || '',
+      containerItem: plan.itemNum || '',
+      descr: plan.itemName || '',
+      um: plan.um || '',
+      location: '',
+      lotSerial: plan.lotserial || '',
+      qtyToShip: plan.qty || 0,
+      remark: ''
+    }))
+
+    ElMessage.success('已加载 ' + plans.length + ' 条出货计划')
+  } catch (error) {
+    ElMessage.error('加载出货计划数据失败:' + (error.message || '未知错误'))
   }
 }
 
 // 加载数据
 const loadData = async (id) => {
   try {
-    const { data } = await getShipmentDetail(id)
-    Object.assign(formData, data)
+    const data = await getShipmentDetail(id)
+    if (data) {
+      formData.id = data.id || ''
+      formData.ordNbr = data.ordNbr || ''
+      formData.soldTo = data.soldTo || ''
+      // 转换日期为时间戳
+      if (data.shipDate) {
+        const dateStr = String(data.shipDate).substring(0, 10)
+        formData.shipDate = new Date(dateStr).getTime()
+      } else {
+        formData.shipDate = null
+      }
+      formData.department = data.department || ''
+      formData.status = data.status || ''
+      formData.remark = data.remark || ''
+      formData.items = data.items || []
+    }
   } catch (error) {
     ElMessage.error('加载数据失败:' + (error.message || '未知错误'))
   }
@@ -265,45 +383,28 @@ const loadData = async (id) => {
 
 // 重置表单
 const resetForm = () => {
-  formRef.value?.resetFields()
-  Object.assign(formData, {
-    lotserial: '',
-    shippingdate: '',
-    shippingsite: '',
-    consignee: '',
-    telephone: '',
-    shippingaddress: '',
-    remark: '',
-    items: []
-  })
+  Object.assign(formData, getDefaultFormData())
+  formRef.value?.clearValidate()
 }
 
 // 添加行
 const handleAddRow = () => {
   formData.items.push({
-    Line: formData.items.length + 1,
-    OrdNbr: '',
-    OrdLine: '',
-    ContainerItem: '',
-    Descr: '',
-    UM: '',
-    Location: '',
-    LotSerial: '',
-    QtyToShip: 0,
-    PickingQty: 0,
-    RealQty: 0,
-    Status: '',
-    Remark: ''
+    ordNbr: formData.ordNbr || '',
+    ordLine: '',
+    containerItem: '',
+    descr: '',
+    um: '',
+    location: '',
+    lotSerial: '',
+    qtyToShip: 0,
+    remark: ''
   })
 }
 
 // 删除行
 const handleDeleteRow = (index) => {
   formData.items.splice(index, 1)
-  // 重新编号
-  formData.items.forEach((item, idx) => {
-    item.Line = idx + 1
-  })
 }
 
 // 提交
@@ -318,11 +419,27 @@ const handleSubmit = async () => {
 
     submitLoading.value = true
 
+    // 转换时间戳为日期字符串
+    const formatDate = (timestamp) => {
+      if (!timestamp) return ''
+      const d = new Date(Number(timestamp))
+      const year = d.getFullYear()
+      const month = String(d.getMonth() + 1).padStart(2, '0')
+      const day = String(d.getDate()).padStart(2, '0')
+      return `${year}-${month}-${day}`
+    }
+
+    const submitData = {
+      ...formData,
+      shipDate: formatDate(formData.shipDate),
+      recId: currentId.value
+    }
+
     if (mode.value === 'add') {
-      await createShipment(formData)
+      await createShipment(submitData)
       ElMessage.success('添加成功')
     } else {
-      await updateShipment(currentId.value, formData)
+      await updateShipment(submitData)
       ElMessage.success('保存成功')
     }
 
@@ -343,9 +460,7 @@ const handleClose = () => {
   currentId.value = null
 }
 
-defineExpose({
-  open
-})
+defineExpose({ open })
 </script>
 
 <style scoped lang="scss">
@@ -353,4 +468,3 @@ defineExpose({
   margin-bottom: 12px;
 }
 </style>
-

+ 7 - 7
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/components/ShippingPlanForm.vue

@@ -224,7 +224,7 @@
     </template>
 
     <!-- 物料选择器 -->
-    <MaterialSelector ref="materialSelectorRef" @select="handleMaterialSelected" />
+    <MaterialSelect ref="materialSelectRef" @select="handleMaterialSelected" />
   </el-dialog>
 </template>
 
@@ -237,7 +237,7 @@ import {
   updateShippingPlan 
 } from '@/api/jiaohuo/order'
 import { ElMessage } from 'element-plus'
-import MaterialSelector from '@/components/MaterialSelector/index.vue'
+import MaterialSelect from './MaterialSelect.vue'
 import { useRoute } from 'vue-router'
 
 const route = useRoute()
@@ -248,7 +248,7 @@ const currentId = ref(null)
 const currentRowIndex = ref(null)
 
 const formRef = ref(null)
-const materialSelectorRef = ref(null)
+const materialSelectRef = ref(null)
 
 const formData = reactive({
   lotSerial: '',
@@ -390,16 +390,16 @@ const resetForm = () => {
 // 选择物料
 const handleSelectMaterial = (index) => {
   currentRowIndex.value = index
-  materialSelectorRef.value.open()
+  materialSelectRef.value.open()
 }
 
 // 物料选择回调
 const handleMaterialSelected = (material) => {
   if (currentRowIndex.value !== null) {
     const row = formData.items[currentRowIndex.value]
-    row.itemNum = material.ItemNum
-    row.itemName = material.Descr
-    row.specification = material.Descr1
+    row.itemNum = material.itemNum
+    row.itemName = material.descr
+    row.specification = material.descr1
   }
 }