src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -17,6 +17,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -113,9 +114,40 @@ } @Override public List customerList(Customer customer) { public List<Map<String, Object>> customerList(Customer customer) { LambdaQueryWrapper<Customer> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.select(Customer::getId, Customer::getCustomerName); return customerMapper.selectMaps(queryWrapper); queryWrapper.select(Customer::getId, Customer::getCustomerName,Customer::getTaxpayerIdentificationNumber); // è·ååå§æ¥è¯¢ç»æ List<Map<String, Object>> result = customerMapper.selectMaps(queryWrapper); // å°ä¸å线å½å转æ¢ä¸ºé©¼å³°å½å return result.stream().map(map -> map.entrySet().stream() .collect(Collectors.toMap( entry -> underlineToCamel(entry.getKey()), Map.Entry::getValue)) ).collect(Collectors.toList()); } /** * ä¸å线å½å转驼峰å½å */ private String underlineToCamel(String param) { if (param == null || "".equals(param.trim())) { return ""; } int len = param.length(); StringBuilder sb = new StringBuilder(len); for (int i = 0; i < len; i++) { char c = param.charAt(i); if (c == '_') { if (++i < len) { sb.append(Character.toUpperCase(param.charAt(i))); } } else { sb.append(Character.toLowerCase(c)); } } return sb.toString(); } } src/main/java/com/ruoyi/common/config/MyBaseMapper.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,20 @@ package com.ruoyi.common.config; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import java.util.List; public interface MyBaseMapper<T> extends BaseMapper<T> { /** * æ¹éæå ¥ï¼ä» æå ¥éç©ºåæ®µï¼ * @param list å®ä½å表 * @return æå ¥æåçè®°å½æ° */ int insertBatchSomeColumn(List<T> list); /** * æ¹éæ´æ°ï¼ä» æ´æ°éç©ºåæ®µï¼ */ int updateBatchSomeColumn(List<T> list); } src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,73 @@ package com.ruoyi.purchase.controller; import javax.servlet.http.HttpServletResponse; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.purchase.pojo.PurchaseLedger; import com.ruoyi.purchase.service.IPurchaseLedgerService; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.framework.aspectj.lang.annotation.Log; import com.ruoyi.framework.aspectj.lang.enums.BusinessType; import com.ruoyi.framework.web.controller.BaseController; import com.ruoyi.framework.web.domain.AjaxResult; import com.ruoyi.framework.web.page.TableDataInfo; import java.util.List; /** * éè´å°è´¦Controller * * @author ruoyi * @date 2025-05-09 */ @RestController @RequestMapping("/system/ledger") @AllArgsConstructor public class PurchaseLedgerController extends BaseController { private IPurchaseLedgerService purchaseLedgerService; /** * æ¥è¯¢éè´å°è´¦å表 */ @GetMapping("/list") public TableDataInfo list(PurchaseLedger purchaseLedger) { startPage(); List<PurchaseLedger> list = purchaseLedgerService.selectPurchaseLedgerList(purchaseLedger); return getDataTable(list); } /** * 导åºéè´å°è´¦å表 */ @Log(title = "éè´å°è´¦", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(HttpServletResponse response, PurchaseLedger purchaseLedger) { List<PurchaseLedger> list = purchaseLedgerService.selectPurchaseLedgerList(purchaseLedger); ExcelUtil<PurchaseLedger> util = new ExcelUtil<PurchaseLedger>(PurchaseLedger.class); util.exportExcel(response, list, "ã请填ååè½åç§°ãæ°æ®"); } /** * æ°å¢ä¿®æ¹éè´å°è´¦ */ @Log(title = "éè´å°è´¦", businessType = BusinessType.INSERT) @PostMapping ("/addOrEditPurchase") public AjaxResult addOrEditPurchase(@RequestBody PurchaseLedger purchaseLedger) { return toAjax(purchaseLedgerService.addOrEditPurchase(purchaseLedger)); } /** * å é¤éè´å°è´¦ */ @Log(title = "éè´å°è´¦", businessType = BusinessType.DELETE) @DeleteMapping("/delPurchase") public AjaxResult remove(@RequestBody Long[] ids) { return toAjax(purchaseLedgerService.deletePurchaseLedgerByIds(ids)); } } src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,15 @@ package com.ruoyi.purchase.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.purchase.pojo.PurchaseLedger; /** * éè´å°è´¦Mapperæ¥å£ * * @author ruoyi * @date 2025-05-09 */ public interface PurchaseLedgerMapper extends BaseMapper<PurchaseLedger> { } src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,97 @@ package com.ruoyi.purchase.pojo; import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.ruoyi.framework.aspectj.lang.annotation.Excel; import lombok.Data; /** * éè´å°è´¦å¯¹è±¡ purchase_ledger * * @author ruoyi * @date 2025-05-09 */ @TableName("purchase_ledger") @Data public class PurchaseLedger { private static final long serialVersionUID = 1L; /** * èªå¢ä¸»é®ID */ @TableId(value = "id", type = IdType.AUTO) private Long id; /** * éè´ååå· */ @Excel(name = "éè´ååå·") private String purchaseContractNumber; /** * ä¾åºååç§° */ @Excel(name = "ä¾åºååç§°") private String supplierName; /** * å½å ¥äººå§å */ @Excel(name = "å½å ¥äººå§å") private String recorderName; /** * éå®ååå· */ @Excel(name = "éå®ååå·") private String salesContractNo; /** * 项ç®åç§° */ @Excel(name = "项ç®åç§°") private String projectName; /** * å½å ¥æ¥æ */ @JsonFormat(pattern = "yyyy-MM-dd") @Excel(name = "å½å ¥æ¥æ", width = 30, dateFormat = "yyyy-MM-dd") private Date entryDate; /** * 夿³¨ */ @Excel(name = "夿³¨") private String remarks; /** * éä»¶ææè·¯å¾æåç§° */ @Excel(name = "éä»¶ææè·¯å¾æåç§°") private String attachmentMaterials; /** * è®°å½å建æ¶é´ */ @JsonFormat(pattern = "yyyy-MM-dd") @Excel(name = "è®°å½å建æ¶é´", width = 30, dateFormat = "yyyy-MM-dd") private Date createdAt; /** * è®°å½æåæ´æ°æ¶é´ */ @JsonFormat(pattern = "yyyy-MM-dd") @Excel(name = "è®°å½æåæ´æ°æ¶é´", width = 30, dateFormat = "yyyy-MM-dd") private Date updatedAt; /** * å ³èéå®å°è´¦ä¸»è¡¨ä¸»é® */ private Long salesLedgerId; } src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,23 @@ package com.ruoyi.purchase.service; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.purchase.pojo.PurchaseLedger; import java.util.List; /** * éè´å°è´¦Serviceæ¥å£ * * @author ruoyi * @date 2025-05-09 */ public interface IPurchaseLedgerService extends IService<PurchaseLedger> { List<PurchaseLedger> selectPurchaseLedgerList(PurchaseLedger purchaseLedger); int addOrEditPurchase(PurchaseLedger purchaseLedger); int deletePurchaseLedgerByIds(Long[] ids); } src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,44 @@ package com.ruoyi.purchase.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.purchase.mapper.PurchaseLedgerMapper; import com.ruoyi.purchase.pojo.PurchaseLedger; import com.ruoyi.purchase.service.IPurchaseLedgerService; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import java.util.Arrays; import java.util.List; /** * éè´å°è´¦Serviceä¸å¡å±å¤ç * * @author ruoyi * @date 2025-05-09 */ @Service @AllArgsConstructor public class PurchaseLedgerServiceImpl extends ServiceImpl<PurchaseLedgerMapper, PurchaseLedger> implements IPurchaseLedgerService { private PurchaseLedgerMapper purchaseLedgerMapper; @Override public List<PurchaseLedger> selectPurchaseLedgerList(PurchaseLedger purchaseLedger) { return purchaseLedgerMapper.selectList(new LambdaQueryWrapper<>()); } @Override public int addOrEditPurchase(PurchaseLedger purchaseLedger) { if (purchaseLedger.getId() == null) { return purchaseLedgerMapper.insert(purchaseLedger); } else { return purchaseLedgerMapper.updateById(purchaseLedger); } } @Override public int deletePurchaseLedgerByIds(Long[] ids) { return purchaseLedgerMapper.deleteBatchIds(Arrays.asList(ids)); } } src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
@@ -82,9 +82,9 @@ */ @Log(title = "éå®å°è´¦", businessType = BusinessType.INSERT) @PostMapping ("/addOrUpdateSalesLedger") public AjaxResult add(@RequestBody SalesLedger salesLedger) public AjaxResult add(@RequestBody SalesLedgerDto salesLedgerDto) { return toAjax(salesLedgerService.addOrUpdateSalesLedger(salesLedger)); return toAjax(salesLedgerService.addOrUpdateSalesLedger(salesLedgerDto)); } /** src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java
@@ -22,5 +22,5 @@ private String remarks; private String attachmentMaterials; private Boolean hasChildren = false; private List<SalesLedgerProduct> children; private List<SalesLedgerProduct> productData; } src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
@@ -3,6 +3,7 @@ import java.util.List; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.sales.pojo.SalesLedger; import org.apache.ibatis.annotations.Param; /** @@ -12,4 +13,10 @@ * @date 2025-05-08 */ public interface SalesLedgerMapper extends BaseMapper<SalesLedger> { /** * æ¥è¯¢æå®æ¥æçææåååºåå· * @param datePart æ¥æé¨åï¼æ ¼å¼ï¼yyyyMMddï¼ * @return åºåå·å表 */ List<Integer> selectSequencesByDate(@Param("datePart") String datePart); } src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java
@@ -1,6 +1,6 @@ package com.ruoyi.sales.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.common.config.MyBaseMapper; import com.ruoyi.sales.pojo.SalesLedgerProduct; /** @@ -9,5 +9,5 @@ * @author ruoyi * @date 2025-05-08 */ public interface SalesLedgerProductMapper extends BaseMapper<SalesLedgerProduct> { public interface SalesLedgerProductMapper extends MyBaseMapper<SalesLedgerProduct> { } src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
@@ -1,5 +1,6 @@ package com.ruoyi.sales.pojo; import java.math.BigDecimal; import java.util.Date; import com.baomidou.mybatisplus.annotation.*; @@ -81,11 +82,16 @@ /** * éä»¶ææï¼å卿件åçç¸å ³ä¿¡æ¯ */ @Excel(name = "éä»¶ææï¼å卿件åçç¸å ³ä¿¡æ¯") private String attachmentMaterials; @TableField(fill = FieldFill.INSERT) private Long tenantId; /** * ååéé¢ï¼äº§åå«ç¨æ»ä»·ï¼ */ @Excel(name = "ç¨ç") private BigDecimal contractAmount; } src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java
@@ -20,7 +20,7 @@ int deleteSalesLedgerByIds(Long[] ids); int addOrUpdateSalesLedger(SalesLedger salesLedger); int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto); List<SalesLedgerDto> getSalesLedgerWithProducts(); } src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -1,6 +1,7 @@ package com.ruoyi.sales.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.basic.mapper.CustomerMapper; import com.ruoyi.basic.pojo.Customer; @@ -12,12 +13,16 @@ import com.ruoyi.sales.pojo.SalesLedgerProduct; import com.ruoyi.sales.service.ISalesLedgerService; import lombok.AllArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -35,6 +40,12 @@ private CustomerMapper customerMapper; private SalesLedgerProductMapper salesLedgerProductMapper; private static final String LOCK_PREFIX = "contract_no_lock:"; private static final long LOCK_WAIT_TIMEOUT = 10; // éçå¾ è¶ æ¶æ¶é´ï¼ç§ï¼ private static final long LOCK_EXPIRE_TIME = 30; // éèªå¨è¿ææ¶é´ï¼ç§ï¼ private final RedisTemplate<String, String> redisTemplate; @Override public List<SalesLedger> selectSalesLedgerList(SalesLedger salesLedger) { @@ -55,7 +66,7 @@ List<SalesLedgerProduct> ledgerProducts = productMap.getOrDefault(ledger.getId(), Collections.emptyList()); if (!ledgerProducts.isEmpty()) { dto.setHasChildren(true); dto.setChildren(ledgerProducts); dto.setProductData(ledgerProducts); } return dto; }).collect(Collectors.toList()); @@ -67,27 +78,139 @@ } @Override @Transactional(rollbackFor = Exception.class) public int deleteSalesLedgerByIds(Long[] ids) { return salesLedgerMapper.deleteBatchIds(Arrays.asList(ids)); List<Long> idList = Arrays.stream(ids) .filter(Objects::nonNull) .collect(Collectors.toList()); if (CollectionUtils.isEmpty(idList)) { return 0; } // 1. å å é¤åè¡¨æ°æ® LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>(); productWrapper.in(SalesLedgerProduct::getSalesLedgerId, idList); salesLedgerProductMapper.delete(productWrapper); // 2. åå é¤ä¸»è¡¨æ°æ® return salesLedgerMapper.deleteBatchIds(idList); } public int addOrUpdateSalesLedger(SalesLedger salesLedger) { LambdaQueryWrapper<Customer> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Customer::getId, salesLedger.getCustomerId()); Customer customer = customerMapper.selectOne(queryWrapper); @Transactional(rollbackFor = Exception.class) public int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto) { // 1. æ ¡éªå®¢æ·ä¿¡æ¯ Customer customer = customerMapper.selectById(salesLedgerDto.getCustomerId()); if (customer == null) { throw new BaseException("æªæ¥è¯¢å°å¯¹åºç Customer ä¿¡æ¯"); throw new BaseException("客æ·ä¸åå¨"); } // 2. DTO转Entity SalesLedger salesLedger = convertToEntity(salesLedgerDto); salesLedger.setCustomerName(customer.getCustomerName()); salesLedger.setTenantId(customer.getTenantId()); return saveOrUpdates(salesLedger); // 3. æ°å¢ææ´æ°ä¸»è¡¨ if (salesLedger.getId() == null) { // çæååç¼å· String contractNo = generateSalesContractNo(); salesLedger.setSalesContractNo(contractNo); salesLedgerMapper.insert(salesLedger); } else { salesLedgerMapper.updateById(salesLedger); } private int saveOrUpdates(SalesLedger salesLedger) { if (salesLedger.getId() == null) { return salesLedgerMapper.insert(salesLedger); } else { return salesLedgerMapper.updateById(salesLedger); // 4. å¤çåè¡¨æ°æ® if (salesLedgerDto.getProductData() != null && !salesLedgerDto.getProductData().isEmpty()) { handleSalesLedgerProducts(salesLedger.getId(), salesLedgerDto.getProductData()); } return 1; // æä½æåè¿å1 } private void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products) { // æIDåç»ï¼åºåæ°å¢åæ´æ°çè®°å½ Map<Boolean, List<SalesLedgerProduct>> partitionedProducts = products.stream() .peek(p -> p.setSalesLedgerId(salesLedgerId)) .collect(Collectors.partitioningBy(p -> p.getId() != null)); List<SalesLedgerProduct> updateList = partitionedProducts.get(true); List<SalesLedgerProduct> insertList = partitionedProducts.get(false); // æ§è¡æ´æ°æä½ if (!updateList.isEmpty()) { salesLedgerProductMapper.updateBatchSomeColumn(updateList); } // æ§è¡æå ¥æä½ if (!insertList.isEmpty()) { salesLedgerProductMapper.insertBatchSomeColumn(insertList); } } private SalesLedger convertToEntity(SalesLedgerDto dto) { SalesLedger entity = new SalesLedger(); BeanUtils.copyProperties(dto, entity); return entity; } @Transactional(readOnly = true) public String generateSalesContractNo() { LocalDate currentDate = LocalDate.now(); String datePart = currentDate.format(DateTimeFormatter.BASIC_ISO_DATE); String lockKey = LOCK_PREFIX + datePart; String lockValue = Thread.currentThread().getId() + "-" + System.nanoTime(); // å¯ä¸æ è¯éææè try { // 1. å°è¯è·ååå¸å¼éï¼å¾ªç¯ç´å°è¶ æ¶ï¼ long startWaitTime = System.currentTimeMillis(); while (System.currentTimeMillis() - startWaitTime < LOCK_WAIT_TIMEOUT * 1000) { // SET key value NX PX 30000ï¼ä» å½éä¸å卿¶è·åï¼è®¾ç½®30ç§è¿æ Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, LOCK_EXPIRE_TIME, TimeUnit.SECONDS); if (Boolean.TRUE.equals(locked)) { break; // æåè·åé } // çæä¼ç é¿å å¿çå¾ try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("è·åéæ¶è¢«ä¸æ", e); } } if (!redisTemplate.hasKey(lockKey)) { throw new RuntimeException("è·åååç¼å·çæé失败ï¼è¶ æ¶"); } // 2. æ¥è¯¢å½å¤©å·²åå¨çåºåå·ï¼ä¸åé»è¾ä¸è´ï¼ List<Integer> existingSequences = salesLedgerMapper.selectSequencesByDate(datePart); int nextSequence = findFirstMissingSequence(existingSequences); return datePart + String.format("%02d", nextSequence); } finally { // 3. éæ¾éï¼ä½¿ç¨Luaèæ¬ä¿è¯ååæ§ï¼é¿å 误å å ¶ä»çº¿ç¨çéï¼ String luaScript = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end"; redisTemplate.execute( new DefaultRedisScript<>(luaScript, Long.class), Collections.singletonList(lockKey), lockValue // åªæææç¸åå¼ççº¿ç¨æè½å é¤é ); } } private int findFirstMissingSequence(List<Integer> sequences) { if (sequences.isEmpty()) { return 1; } // æåºåæ¥æ¾ç¬¬ä¸ä¸ªç¼ºå¤±çæ£æ´æ°ï¼ä¸åé»è¾ä¸è´ï¼ sequences.sort(Integer::compareTo); int next = 1; for (int seq : sequences) { if (seq == next) { next++; } else if (seq > next) { break; } } return next; } } src/main/resources/application.yml
@@ -100,7 +100,7 @@ # MyBatis Plusé ç½® mybatis-plus: # æç´¢æå®å å«å æ ¹æ®èªå·±çé¡¹ç®æ¥ typeAliasesPackage: com.ruoyi.basic.**.pojo typeAliasesPackage: com.ruoyi.**.pojo # é ç½®mapperçæ«æï¼æ¾å°ææçmapper.xmlæ å°æä»¶ mapperLocations: classpath*:mapper/**/*Mapper.xml # å è½½å ¨å±çé ç½®æä»¶ src/main/resources/mapper/sales/SalesLedgerMapper.xml
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ruoyi.sales.mapper.SalesLedgerMapper"> <select id="selectSequencesByDate" resultType="java.lang.Integer"> SELECT CAST(SUBSTR(sales_contract_no, 9, 2) AS SIGNED) FROM sales_ledger WHERE SUBSTR(sales_contract_no, 1, 8) = #{datePart} </select> </mapper>