Ver código fonte

销售管理模块后端代码

lbyzx123 1 semana atrás
pai
commit
7f94d6e566

+ 51 - 11
ruoyi-admin/src/main/java/com/ruoyi/web/base/controller/SalesAllowanceController.java

@@ -33,6 +33,8 @@ import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -40,8 +42,6 @@ import java.util.stream.Collectors;
 
 import static com.ruoyi.common.core.domain.AjaxResult.success;
 import static com.ruoyi.common.utils.SecurityUtils.getUsername;
-import static com.ruoyi.web.base.util.NumUtils.generateString;
-import static com.ruoyi.web.base.util.NumUtils.substringToInt;
 
 @RestController
 @Api(tags = "销售折让单")
@@ -245,16 +245,15 @@ public class SalesAllowanceController {
         return success(allowance);
     }
 
-    @ApiOperation("获取最新折让编号")
-    @PostMapping("/getAllowanceNum")
-    public AjaxResult getAllowanceNum(HttpServletRequest request) throws Exception {
+    @ApiOperation("获取新增销售折让单页面数据")
+    @PostMapping("/getAddPageData")
+    public AjaxResult getAddPageData(HttpServletRequest request) throws Exception {
         String orgId = tokenService.getLoginOrgId(request);
-        SalesAllowance one = salesAllowanceService.getOne(new QueryWrapper<SalesAllowance>()
-                .eq("org_id", orgId).eq("del_flag", "0").orderByDesc("id").last("limit 1"));
-        if (one == null || StringUtils.isBlank(one.getAllowanceNum())) {
-            return success(generateString("zr", 1));
-        }
-        return success(generateString("zr", substringToInt(one.getAllowanceNum())));
+        String tomorrow = LocalDate.now().plusDays(1).format(DateTimeFormatter.ISO_LOCAL_DATE);
+        Map<String, Object> data = new HashMap<String, Object>();
+        data.put("allowanceDate", tomorrow);
+        data.put("allowanceNum", generateSalesAllowanceNum(orgId));
+        return success(data);
     }
 
     @ApiOperation("按销售出库单生成折让清单行(追加用,优先传主键id以避免单号重复时误命中)")
@@ -497,6 +496,47 @@ public class SalesAllowanceController {
         }
     }
 
+    /**
+     * 单据编号规则:
+     * XXZR + 当前日期后一天的yyyyMMdd + 3位递增序号(不足补零,从001开始)
+     */
+    private String generateSalesAllowanceNum(String orgId) throws Exception {
+        if (StringUtils.isBlank(orgId)) {
+            throw new Exception("orgId不能为空");
+        }
+        String tomorrowYmd = LocalDate.now().plusDays(1).format(DateTimeFormatter.BASIC_ISO_DATE);
+        String base = "XXZR" + tomorrowYmd;
+
+        SalesAllowance last = salesAllowanceService.getOne(new QueryWrapper<SalesAllowance>()
+                .eq("org_id", orgId)
+                .eq("del_flag", "0")
+                .like("allowance_num", base)
+                .orderByDesc("allowance_num")
+                .last("limit 1"));
+
+        int startSeq = 1;
+        if (last != null && StringUtils.isNotBlank(last.getAllowanceNum()) && last.getAllowanceNum().length() >= 3) {
+            String suffix = last.getAllowanceNum().substring(last.getAllowanceNum().length() - 3);
+            try {
+                startSeq = Integer.parseInt(suffix) + 1;
+            } catch (Exception ignored) {
+                startSeq = 1;
+            }
+        }
+
+        for (int seq = startSeq; seq <= 999; seq++) {
+            String candidate = base + String.format("%03d", seq);
+            long occupied = salesAllowanceService.count(new QueryWrapper<SalesAllowance>()
+                    .eq("org_id", orgId)
+                    .eq("allowance_num", candidate)
+                    .eq("del_flag", "0"));
+            if (occupied == 0) {
+                return candidate;
+            }
+        }
+        throw new Exception("无法生成唯一销售折让单编号");
+    }
+
     private void deleteItemsByAllowanceNum(String orgId, String allowanceNum) {
         if (StringUtils.isBlank(orgId) || StringUtils.isBlank(allowanceNum)) {
             return;

+ 52 - 11
ruoyi-admin/src/main/java/com/ruoyi/web/base/controller/SalesDeliveryController.java

@@ -26,16 +26,17 @@ import org.springframework.web.bind.annotation.*;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
 import java.util.Calendar;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import static com.ruoyi.common.core.domain.AjaxResult.success;
 import static com.ruoyi.common.utils.SecurityUtils.getUsername;
-import static com.ruoyi.web.base.util.NumUtils.generateString;
-import static com.ruoyi.web.base.util.NumUtils.substringToInt;
 
 @RestController
 @Api(tags = "销售出库单")
@@ -232,16 +233,15 @@ public class SalesDeliveryController {
         return success(delivery);
     }
 
-    @ApiOperation("获取最新销售出库单编号")
-    @PostMapping("/getDeliveryNum")
-    public AjaxResult getDeliveryNum(HttpServletRequest request) throws Exception {
+    @ApiOperation("获取新增销售出库单页面数据")
+    @PostMapping("/getAddPageData")
+    public AjaxResult getAddPageData(HttpServletRequest request) throws Exception {
         String orgId = tokenService.getLoginOrgId(request);
-        SalesDelivery one = salesDeliveryService.getOne(new QueryWrapper<SalesDelivery>()
-                .eq("org_id", orgId).eq("del_flag", "0").orderByDesc("id").last("limit 1"));
-        if (one == null || StringUtils.isBlank(one.getDeliveryNum())) {
-            return success(generateString("xc", 1));
-        }
-        return success(generateString("xc", substringToInt(one.getDeliveryNum())));
+        String tomorrow = LocalDate.now().plusDays(1).format(DateTimeFormatter.ISO_LOCAL_DATE);
+        Map<String, Object> data = new HashMap<String, Object>();
+        data.put("documentDate", tomorrow);
+        data.put("deliveryNum", generateSalesDeliveryNum(orgId));
+        return success(data);
     }
 
     private QueryWrapper<SalesDelivery> buildWrapper(SalesDelivery query, String orgId, String goodsNum) {
@@ -384,6 +384,47 @@ public class SalesDeliveryController {
         }
     }
 
+    /**
+     * 单据编号规则:
+     * XSCK + 当前日期后一天的yyyyMMdd + 5位递增序号(不足补零,从00001开始)
+     */
+    private String generateSalesDeliveryNum(String orgId) throws Exception {
+        if (StringUtils.isBlank(orgId)) {
+            throw new Exception("orgId不能为空");
+        }
+        String tomorrowYmd = LocalDate.now().plusDays(1).format(DateTimeFormatter.BASIC_ISO_DATE);
+        String base = "XSCK" + tomorrowYmd;
+
+        SalesDelivery last = salesDeliveryService.getOne(new QueryWrapper<SalesDelivery>()
+                .eq("org_id", orgId)
+                .eq("del_flag", "0")
+                .like("delivery_num", base)
+                .orderByDesc("delivery_num")
+                .last("limit 1"));
+
+        int startSeq = 1;
+        if (last != null && StringUtils.isNotBlank(last.getDeliveryNum()) && last.getDeliveryNum().length() >= 5) {
+            String suffix = last.getDeliveryNum().substring(last.getDeliveryNum().length() - 5);
+            try {
+                startSeq = Integer.parseInt(suffix) + 1;
+            } catch (Exception ignored) {
+                startSeq = 1;
+            }
+        }
+
+        for (int seq = startSeq; seq <= 99999; seq++) {
+            String candidate = base + String.format("%05d", seq);
+            long occupied = salesDeliveryService.count(new QueryWrapper<SalesDelivery>()
+                    .eq("org_id", orgId)
+                    .eq("delivery_num", candidate)
+                    .eq("del_flag", "0"));
+            if (occupied == 0) {
+                return candidate;
+            }
+        }
+        throw new Exception("无法生成唯一销售出库单编号");
+    }
+
     private String formatMoney(BigDecimal n) {
         if (n == null) return null;
         return n.stripTrailingZeros().toPlainString();

+ 90 - 14
ruoyi-admin/src/main/java/com/ruoyi/web/base/controller/SalesOrderController.java

@@ -35,7 +35,11 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
 import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
@@ -49,8 +53,6 @@ import java.util.stream.Collectors;
 
 import static com.ruoyi.common.core.domain.AjaxResult.success;
 import static com.ruoyi.common.utils.SecurityUtils.getUsername;
-import static com.ruoyi.web.base.util.NumUtils.generateString;
-import static com.ruoyi.web.base.util.NumUtils.substringToInt;
 
 @RestController
 @Api(tags = "销售订单")
@@ -257,19 +259,16 @@ public class SalesOrderController {
         return success(order);
     }
 
-    @ApiOperation("获取最新销售订单编号")
-    @PostMapping("/getOrderNum")
-    public AjaxResult getOrderNum(HttpServletRequest request) throws Exception {
+    @ApiOperation("获取新增销售订单页面数据")
+    @PostMapping("/getAddPageData")
+    public AjaxResult getAddPageData(HttpServletRequest request) throws Exception {
         String orgId = tokenService.getLoginOrgId(request);
-        SalesOrder one = salesOrderService.getOne(new QueryWrapper<SalesOrder>()
-                .eq("org_id", orgId)
-                .eq("del_flag", "0")
-                .orderByDesc("id")
-                .last("limit 1"));
-        if (one == null || StringUtils.isBlank(one.getOrderNum())) {
-            return success(generateString("xs", 1));
-        }
-        return success(generateString("xs", substringToInt(one.getOrderNum())));
+        String tomorrow = LocalDate.now().plusDays(1).format(DateTimeFormatter.ISO_LOCAL_DATE);
+        Map<String, Object> data = new HashMap<String, Object>();
+        data.put("documentDate", tomorrow);
+        data.put("saleDate", tomorrow);
+        data.put("orderNum", generateSalesOrderNum(orgId));
+        return success(data);
     }
 
     @ApiOperation("按货品编号、客户编号、单据日期查询物料及价格")
@@ -622,6 +621,83 @@ public class SalesOrderController {
         }
     }
 
+    /**
+     * 单据编号规则:
+     * XSDD + 当前日期后一天的yyyyMMdd + 3位机器标识 + 3位递增序号(不足补零,从001开始)
+     */
+    private String generateSalesOrderNum(String orgId) throws Exception {
+        if (StringUtils.isBlank(orgId)) {
+            throw new Exception("orgId不能为空");
+        }
+        String tomorrowYmd = LocalDate.now().plusDays(1).format(DateTimeFormatter.BASIC_ISO_DATE);
+        String machineCode = getMachineCode3Digits();
+        String base = "XSDD" + tomorrowYmd + machineCode;
+
+        // 取该 base 前缀下当前最大序号(最后3位),若不存在则从 001 开始
+        SalesOrder last = salesOrderService.getOne(new QueryWrapper<SalesOrder>()
+                .eq("org_id", orgId)
+                .eq("del_flag", "0")
+                // 由于订单编号固定前缀格式,这里用 like 匹配前缀即可
+                .like("order_num", base)
+                .orderByDesc("order_num")
+                .last("limit 1"));
+
+        int startSeq = 1;
+        if (last != null && StringUtils.isNotBlank(last.getOrderNum()) && last.getOrderNum().length() >= 3) {
+            String suffix = last.getOrderNum().substring(last.getOrderNum().length() - 3);
+            try {
+                startSeq = Integer.parseInt(suffix) + 1;
+            } catch (Exception ignored) {
+                startSeq = 1;
+            }
+        }
+
+        for (int seq = startSeq; seq <= 999; seq++) {
+            String candidate = base + String.format("%03d", seq);
+            long occupied = salesOrderService.count(new QueryWrapper<SalesOrder>()
+                    .eq("org_id", orgId)
+                    .eq("order_num", candidate)
+                    .eq("del_flag", "0"));
+            if (occupied == 0) {
+                return candidate;
+            }
+        }
+        throw new Exception("无法生成唯一销售订单编号");
+    }
+
+    /**
+     * 生成 3 位机器标识:优先取网卡 MAC(取前向量 hash),取不到则使用主机名 hash。
+     */
+    private String getMachineCode3Digits() {
+        try {
+            byte[] mac = null;
+            for (NetworkInterface ni : Collections.list(NetworkInterface.getNetworkInterfaces())) {
+                if (ni == null || !ni.isUp() || ni.isLoopback() || ni.isVirtual()) {
+                    continue;
+                }
+                byte[] addr = ni.getHardwareAddress();
+                if (addr != null && addr.length > 0) {
+                    mac = addr;
+                    break;
+                }
+            }
+
+            int val = 0;
+            if (mac != null) {
+                for (byte b : mac) {
+                    val = (val * 31 + (b & 0xff)) % 1000;
+                }
+            } else {
+                String host = InetAddress.getLocalHost().getHostName();
+                val = Math.abs(host.hashCode()) % 1000;
+            }
+            return String.format("%03d", val);
+        } catch (Exception e) {
+            // 极端情况下兜底,确保接口不因机器标识失败而报错
+            return "001";
+        }
+    }
+
     /** 小计=单价×基本计量数量;总计金额=各明细小计之和 */
     private void recalcGoodsSubTotalsAndOrderTotal(SalesOrder order) {
         List<SalesOrderGoods> goods = order.getGoods();

+ 64 - 12
ruoyi-admin/src/main/java/com/ruoyi/web/base/controller/SalesPriceController.java

@@ -62,8 +62,8 @@ public class SalesPriceController {
     @Transactional
     public AjaxResult add(@RequestBody SalesPrice price, HttpServletRequest request) throws Exception {
         String orgId = tokenService.getLoginOrgId(request);
-        // 新增时后台统一生成价格单编号:P + 当前时间后一天yyyyMMdd
-        price.setPriceNum(generatePriceNum());
+        // 新增时后台统一生成价格单编号:确保组织内唯一
+        price.setPriceNum(nextAvailablePriceNum(orgId));
         validateBeforeSave(price, orgId, true);
         price.setOrgId(orgId);
         price.setDelFlag("0");
@@ -216,6 +216,18 @@ public class SalesPriceController {
         return success(items);
     }
 
+    @ApiOperation("获取新增价格页面数据")
+    @PreAuthorize("@ss.hasPermi('base:price:add')")
+    @PostMapping("/getAddPageData")
+    public AjaxResult getAddPageData(HttpServletRequest request) {
+        // 执行日期/失效日期均默认取“当前日期后一天”
+        String tomorrow = LocalDate.now().plusDays(1).format(DateTimeFormatter.ISO_LOCAL_DATE);
+        Map<String, Object> data = new HashMap<String, Object>();
+        data.put("effectiveDate", tomorrow);
+        data.put("expireDate", tomorrow);
+        return success(data);
+    }
+
     @ApiOperation("价格管理导出")
     @PreAuthorize("@ss.hasPermi('base:price:export')")
     @PostMapping("/export")
@@ -308,6 +320,10 @@ public class SalesPriceController {
         if (price == null) {
             throw new Exception("参数不能为空");
         }
+        if (StringUtils.isBlank(price.getPriceType())) {
+            throw new Exception("价格类型不能为空");
+        }
+        price.setPriceType(price.getPriceType().trim());
         if (!isAdd && StringUtils.isBlank(price.getPriceNum())) {
             throw new Exception("价格单编号不能为空");
         }
@@ -317,9 +333,25 @@ public class SalesPriceController {
         if (price.getExpireDate().before(price.getEffectiveDate())) {
             throw new Exception("失效日期不能早于执行日期");
         }
+
+        // 同一价格类型 + 同一执行日期(未删除)仅允许一条主表记录
+        // effective_date 为 date 类型,直接按日期精确匹配
+        QueryWrapper<SalesPrice> effectiveWrapper = new QueryWrapper<SalesPrice>()
+                .eq("org_id", orgId)
+                .eq("del_flag", "0")
+                .eq("price_type", price.getPriceType())
+                .eq("effective_date", price.getEffectiveDate());
+        if (!isAdd && price.getId() != null) {
+            effectiveWrapper.ne("id", price.getId());
+        }
+        if (salesPriceService.count(effectiveWrapper) > 0) {
+            throw new Exception("该价格类型在执行日期已存在价格单");
+        }
+
         QueryWrapper<SalesPrice> numWrapper = new QueryWrapper<SalesPrice>()
                 .eq("org_id", orgId)
                 .eq("price_num", price.getPriceNum())
+                .eq("price_type", price.getPriceType())
                 .eq("del_flag", "0");
         if (!isAdd && price.getId() != null) {
             numWrapper.ne("id", price.getId());
@@ -340,23 +372,43 @@ public class SalesPriceController {
         }
     }
 
-    private String generatePriceNum() {
-        String ymd = LocalDate.now().plusDays(1).format(DateTimeFormatter.BASIC_ISO_DATE);
-        return "P" + ymd;
-    }
-
     /**
-     * 生成组织内未占用的价格单编号:P+次日yyyyMMdd,若已存在则依次尝试 P...-1、P...-2…
+     * 生成组织内未占用的价格单编号:
+     * P + 次日yyyyMMdd + 5位递增序号(不足补0,从00001开始)
      */
     private String nextAvailablePriceNum(String orgId) throws Exception {
         String ymd = LocalDate.now().plusDays(1).format(DateTimeFormatter.BASIC_ISO_DATE);
         String prefix = "P" + ymd;
-        for (int n = 0; n < 10000; n++) {
-            String candidate = n == 0 ? prefix : prefix + "-" + n;
-            if (salesPriceService.count(new QueryWrapper<SalesPrice>()
+
+        // 先取已存在最大后缀,作为起始序号(避免每次都从 00001 开始扫描)
+        SalesPrice last = salesPriceService.getOne(new QueryWrapper<SalesPrice>()
+                .eq("org_id", orgId)
+                .eq("del_flag", "0")
+                .like("price_num", prefix)
+                .orderByDesc("price_num")
+                .last("limit 1"));
+
+        int startSeq = 1;
+        if (last != null && StringUtils.isNotBlank(last.getPriceNum())) {
+            String pn = last.getPriceNum().trim();
+            // pn 应为:PyyyyMMdd + 5位数字
+            if (pn.startsWith(prefix) && pn.length() == prefix.length() + 5) {
+                String suffix = pn.substring(prefix.length());
+                try {
+                    startSeq = Integer.parseInt(suffix) + 1;
+                } catch (Exception ignored) {
+                    startSeq = 1;
+                }
+            }
+        }
+
+        for (int seq = startSeq; seq <= 99999; seq++) {
+            String candidate = prefix + String.format("%05d", seq);
+            long occupied = salesPriceService.count(new QueryWrapper<SalesPrice>()
                     .eq("org_id", orgId)
                     .eq("price_num", candidate)
-                    .eq("del_flag", "0")) == 0) {
+                    .eq("del_flag", "0"));
+            if (occupied == 0) {
                 return candidate;
             }
         }

+ 51 - 11
ruoyi-admin/src/main/java/com/ruoyi/web/base/controller/SalesReturnController.java

@@ -34,14 +34,14 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
 import static com.ruoyi.common.core.domain.AjaxResult.success;
 import static com.ruoyi.common.utils.SecurityUtils.getUsername;
-import static com.ruoyi.web.base.util.NumUtils.generateString;
-import static com.ruoyi.web.base.util.NumUtils.substringToInt;
 
 @RestController
 @Api(tags = "销售退回单")
@@ -249,16 +249,15 @@ public class SalesReturnController {
         return success(salesReturn);
     }
 
-    @ApiOperation("获取最新销售退回单编号")
-    @PostMapping("/getReturnNum")
-    public AjaxResult getReturnNum(HttpServletRequest request) throws Exception {
+    @ApiOperation("获取新增销售退回单页面数据")
+    @PostMapping("/getAddPageData")
+    public AjaxResult getAddPageData(HttpServletRequest request) throws Exception {
         String orgId = tokenService.getLoginOrgId(request);
-        SalesReturn one = salesReturnService.getOne(new QueryWrapper<SalesReturn>()
-                .eq("org_id", orgId).eq("del_flag", "0").orderByDesc("id").last("limit 1"));
-        if (one == null || StringUtils.isBlank(one.getReturnNum())) {
-            return success(generateString("xt", 1));
-        }
-        return success(generateString("xt", substringToInt(one.getReturnNum())));
+        String tomorrow = LocalDate.now().plusDays(1).format(DateTimeFormatter.ISO_LOCAL_DATE);
+        Map<String, Object> data = new HashMap<String, Object>();
+        data.put("documentDate", tomorrow);
+        data.put("returnNum", generateSalesReturnNum(orgId));
+        return success(data);
     }
 
     @ApiOperation("按销售出库单编号回填退回单")
@@ -475,6 +474,47 @@ public class SalesReturnController {
         }
     }
 
+    /**
+     * 单据编号规则:
+     * XSTH + 当前日期后一天的yyyyMMdd + 3位递增序号(不足补零,从001开始)
+     */
+    private String generateSalesReturnNum(String orgId) throws Exception {
+        if (StringUtils.isBlank(orgId)) {
+            throw new Exception("orgId不能为空");
+        }
+        String tomorrowYmd = LocalDate.now().plusDays(1).format(DateTimeFormatter.BASIC_ISO_DATE);
+        String base = "XSTH" + tomorrowYmd;
+
+        SalesReturn last = salesReturnService.getOne(new QueryWrapper<SalesReturn>()
+                .eq("org_id", orgId)
+                .eq("del_flag", "0")
+                .like("return_num", base)
+                .orderByDesc("return_num")
+                .last("limit 1"));
+
+        int startSeq = 1;
+        if (last != null && StringUtils.isNotBlank(last.getReturnNum()) && last.getReturnNum().length() >= 3) {
+            String suffix = last.getReturnNum().substring(last.getReturnNum().length() - 3);
+            try {
+                startSeq = Integer.parseInt(suffix) + 1;
+            } catch (Exception ignored) {
+                startSeq = 1;
+            }
+        }
+
+        for (int seq = startSeq; seq <= 999; seq++) {
+            String candidate = base + String.format("%03d", seq);
+            long occupied = salesReturnService.count(new QueryWrapper<SalesReturn>()
+                    .eq("org_id", orgId)
+                    .eq("return_num", candidate)
+                    .eq("del_flag", "0"));
+            if (occupied == 0) {
+                return candidate;
+            }
+        }
+        throw new Exception("无法生成唯一销售退回单编号");
+    }
+
     private void enrichGoodsDisplay(List<SalesReturnGoods> goods, String orgId) {
         if (goods == null || goods.isEmpty()) return;
         for (SalesReturnGoods g : goods) {