Merge branch 'dev_New_pro' into dev_宁夏_万通新型建材
已添加9个文件
已重命名1个文件
已修改53个文件
已删除6个文件
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.util.Date; |
| | | |
| | | @Data |
| | | @Schema(name = "SalesOutboundDto", description = "è´¢å¡ç®¡ç--éå®åºåºå°è´¦(ä¼ å)") |
| | | public class SalesOutboundDto { |
| | | |
| | | @Schema(description = "åºåºåå·") |
| | | private String outboundBatches; |
| | | |
| | | @Schema(description = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private Date startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private Date endDate; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.dto; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.util.Date; |
| | | |
| | | @Data |
| | | @Schema(name = "SalesReturnDto", description = "è´¢å¡ç®¡ç--éå®éè´§å°è´¦(ä¼ å)") |
| | | public class SalesReturnDto { |
| | | |
| | | @Schema(description = "éè´§åå·") |
| | | private String returnNo; |
| | | |
| | | @Schema(description = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private Date startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private Date endDate; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.Date; |
| | | |
| | | @Data |
| | | @Schema(name = "SalesOutboundVo", description = "è´¢å¡ç®¡ç--éå®åºåºå°è´¦(è¿å)") |
| | | @ExcelIgnoreUnannotated |
| | | public class SalesOutboundVo { |
| | | |
| | | @Schema(description = "åºåºåid") |
| | | private Long id; |
| | | |
| | | @Schema(description = "åºåºåå·") |
| | | @Excel(name = "åºåºåå·") |
| | | private String outboundBatches; |
| | | |
| | | @Schema(description = "客æ·åç§°") |
| | | @Excel(name = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "åºåºæ¥æ") |
| | | @Excel(name = "åºåºæ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private Date shippingDate; |
| | | |
| | | @Schema(description = "产ååç§°") |
| | | @Excel(name = "产ååç§°") |
| | | private String productName; |
| | | |
| | | @Schema(description = "产åè§æ ¼") |
| | | @Excel(name = "产åè§æ ¼") |
| | | private String specificationModel; |
| | | |
| | | @Schema(description = "åºåºæ°é") |
| | | @Excel(name = "åºåºæ°é") |
| | | private BigDecimal stockOutNum; |
| | | |
| | | @Schema(description = "åè´§ç¼å·") |
| | | @Excel(name = "åè´§ç¼å·") |
| | | private String shippingNo; |
| | | |
| | | @Schema(description = "éå®è®¢åå·") |
| | | @Excel(name = "éå®è®¢åå·") |
| | | private String salesContractNo; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.bean.vo; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @Schema(name = "SalesReturnVo", description = "è´¢å¡ç®¡ç--éå®éè´§å°è´¦(è¿å)") |
| | | @ExcelIgnoreUnannotated |
| | | public class SalesReturnVo { |
| | | |
| | | @Schema(description = "éè´§åid") |
| | | private Long id; |
| | | |
| | | @Excel(name = "éè´§åå·") |
| | | @Schema(description = "éè´§åå·") |
| | | private String returnNo; |
| | | |
| | | @Schema(description = "客æ·åç§°") |
| | | @Excel(name = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "å
³èåè´§åå·") |
| | | @Excel(name = "å
³èåè´§åå·") |
| | | private String shippingNo; |
| | | |
| | | @Schema(description = "éè´§æ¥æ") |
| | | @Excel(name = "éè´§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private LocalDateTime makeTime; |
| | | |
| | | @Schema(description = "鿬¾æ»é¢") |
| | | @Excel(name = "鿬¾æ»é¢") |
| | | private BigDecimal refundAmount; |
| | | |
| | | @Schema(description = "éè´§åå ") |
| | | @Excel(name = "éè´§åå ") |
| | | private String returnReason; |
| | | |
| | | @Schema(description = "éå®è®¢åå·") |
| | | @Excel(name = "éå®è®¢åå·") |
| | | private String salesContractNo; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.controller; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.SalesOutboundDto; |
| | | import com.ruoyi.account.bean.dto.SalesReturnDto; |
| | | import com.ruoyi.account.bean.vo.SalesOutboundVo; |
| | | import com.ruoyi.account.bean.vo.SalesReturnVo; |
| | | import com.ruoyi.account.service.AccountSalesService; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ççéå®é¨å å端æ§å¶å¨ |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/accountSales") |
| | | @RequiredArgsConstructor |
| | | @Tag(name = "è´¢å¡ç®¡ççéå®é¨å") |
| | | public class AccountSalesController { |
| | | |
| | | private final AccountSalesService accountSalesService; |
| | | |
| | | @GetMapping("/listPageByOutbound") |
| | | @Log(title = "éå®åºåºå°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--éå®åºåºå°è´¦") |
| | | public R<IPage<SalesOutboundVo>> listPageByOutbound(Page page, SalesOutboundDto salesOutboundDto) { |
| | | IPage<SalesOutboundVo> listPage = accountSalesService.listPageByOutbound(page,salesOutboundDto); |
| | | return R.ok(listPage); |
| | | } |
| | | |
| | | @PostMapping("/exportAccountSalesOutbound") |
| | | @Operation(summary = "导åºéå®åºåºæä»¶") |
| | | @Log(title = "导åºéå®åºåºæä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountSalesOutbound(HttpServletResponse response,SalesOutboundDto salesOutboundDto) { |
| | | accountSalesService.exportAccountSalesOutbound(response,salesOutboundDto); |
| | | } |
| | | |
| | | @GetMapping("/listPageByReturn") |
| | | @Log(title = "éå®éè´§å°è´¦", businessType = BusinessType.OTHER) |
| | | @Operation(summary = "è´¢å¡ç®¡ç--éå®éè´§å°è´¦") |
| | | public R<IPage<SalesReturnVo>> listPageBySalesReturn(Page page, SalesReturnDto salesReturnDto) { |
| | | IPage<SalesReturnVo> listPage = accountSalesService.listPageBySalesReturn(page,salesReturnDto); |
| | | return R.ok(listPage); |
| | | } |
| | | |
| | | @PostMapping("/exportAccountSalesReturn") |
| | | @Operation(summary = "导åºéå®éè´§æä»¶") |
| | | @Log(title = "导åºéå®éè´§æä»¶", businessType = BusinessType.EXPORT) |
| | | public void exportAccountSalesReturn(HttpServletResponse response,SalesReturnDto salesReturnDto) { |
| | | accountSalesService.exportAccountSalesReturn(response,salesReturnDto); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.SalesOutboundDto; |
| | | import com.ruoyi.account.bean.dto.SalesReturnDto; |
| | | import com.ruoyi.account.bean.vo.SalesOutboundVo; |
| | | import com.ruoyi.account.bean.vo.SalesReturnVo; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ççéå®é¨å æå¡ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | public interface AccountSalesService { |
| | | |
| | | IPage<SalesOutboundVo> listPageByOutbound(Page page, SalesOutboundDto salesOutboundDto); |
| | | |
| | | void exportAccountSalesOutbound(HttpServletResponse response, SalesOutboundDto salesOutboundDto); |
| | | |
| | | IPage<SalesReturnVo> listPageBySalesReturn(Page page, SalesReturnDto salesReturnDto); |
| | | |
| | | void exportAccountSalesReturn(HttpServletResponse response, SalesReturnDto salesReturnDto); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.account.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.SalesOutboundDto; |
| | | import com.ruoyi.account.bean.dto.SalesReturnDto; |
| | | import com.ruoyi.account.bean.vo.SalesOutboundVo; |
| | | import com.ruoyi.account.bean.vo.SalesReturnVo; |
| | | import com.ruoyi.account.service.AccountSalesService; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper; |
| | | import com.ruoyi.sales.mapper.ShippingInfoMapper; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * è´¢å¡ç®¡ççéå®é¨å æå¡å®ç°ç±» |
| | | * </p> |
| | | * |
| | | * @author è¯å¯¼è½¯ä»¶ï¼æ±èï¼æéå
¬å¸ |
| | | * @since 2026-05-07 04:45:30 |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class AccountSalesServiceImpl implements AccountSalesService { |
| | | |
| | | private final ShippingInfoMapper shippingInfoMapper; |
| | | |
| | | private final ReturnManagementMapper returnManagementMapper; |
| | | |
| | | @Override |
| | | public IPage<SalesOutboundVo> listPageByOutbound(Page page, SalesOutboundDto salesOutboundDto) { |
| | | return shippingInfoMapper.listPageByOutbound(page,salesOutboundDto); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountSalesOutbound(HttpServletResponse response, SalesOutboundDto salesOutboundDto) { |
| | | List<SalesOutboundVo> list = shippingInfoMapper.listPageByOutbound(new Page(1,-1),salesOutboundDto).getRecords(); |
| | | ExcelUtil<SalesOutboundVo> util = new ExcelUtil<>(SalesOutboundVo.class); |
| | | util.exportExcel(response, list , "éå®åºåº"); |
| | | } |
| | | |
| | | @Override |
| | | public IPage<SalesReturnVo> listPageBySalesReturn(Page page, SalesReturnDto salesReturnDto) { |
| | | return returnManagementMapper.listPageBySalesReturn(page,salesReturnDto); |
| | | } |
| | | |
| | | @Override |
| | | public void exportAccountSalesReturn(HttpServletResponse response, SalesReturnDto salesReturnDto) { |
| | | List<SalesReturnVo> list = returnManagementMapper.listPageBySalesReturn(new Page(1,-1),salesReturnDto).getRecords(); |
| | | ExcelUtil<SalesReturnVo> util = new ExcelUtil<>(SalesReturnVo.class); |
| | | util.exportExcel(response, list , "éå®éè´§"); |
| | | } |
| | | } |
| | |
| | | package com.ruoyi.ai.controller; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.ruoyi.ai.assistant.PurchaseAgent; |
| | | import com.ruoyi.ai.assistant.PurchaseIntentExecutor; |
| | | import com.ruoyi.ai.bean.ChatForm; |
| | | import com.ruoyi.ai.bean.PurchaseAiConfirmRequest; |
| | | import com.ruoyi.ai.context.AiSessionUserContext; |
| | | import com.ruoyi.ai.service.AiChatSessionService; |
| | | import com.ruoyi.ai.service.AiFileTextExtractor; |
| | | import com.ruoyi.ai.store.MongoChatMemoryStore; |
| | | import com.ruoyi.basic.dto.StorageBlobVO; |
| | | import com.ruoyi.basic.mapper.SupplierManageMapper; |
| | | import com.ruoyi.basic.pojo.SupplierManage; |
| | | import com.ruoyi.basic.service.StorageBlobService; |
| | | import com.ruoyi.ai.service.PurchaseAiService; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.framework.security.LoginUser; |
| | | import com.ruoyi.framework.web.controller.BaseController; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import com.ruoyi.purchase.dto.PurchaseLedgerDto; |
| | | import com.ruoyi.purchase.dto.PurchaseReturnOrderDto; |
| | | import com.ruoyi.purchase.pojo.PaymentRegistration; |
| | | import com.ruoyi.purchase.service.IPaymentRegistrationService; |
| | | import com.ruoyi.purchase.service.IPurchaseLedgerService; |
| | | import com.ruoyi.purchase.service.PurchaseReturnOrdersService; |
| | | import com.ruoyi.sales.pojo.SalesLedgerProduct; |
| | | import dev.langchain4j.data.image.Image; |
| | | import dev.langchain4j.data.message.AiMessage; |
| | | import dev.langchain4j.data.message.ChatMessage; |
| | | import dev.langchain4j.data.message.Content; |
| | | import dev.langchain4j.data.message.ImageContent; |
| | | import dev.langchain4j.data.message.SystemMessage; |
| | | import dev.langchain4j.data.message.TextContent; |
| | | import dev.langchain4j.data.message.UserMessage; |
| | | import dev.langchain4j.model.chat.StreamingChatLanguageModel; |
| | | import dev.langchain4j.model.chat.response.ChatResponse; |
| | | import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import org.springframework.beans.factory.annotation.Qualifier; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.DeleteMapping; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PathVariable; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.Base64; |
| | | import java.util.Arrays; |
| | | import java.time.LocalDate; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.format.DateTimeParseException; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.Map; |
| | | import java.util.NoSuchElementException; |
| | | import java.util.UUID; |
| | | import java.nio.file.Files; |
| | | |
| | | @Tag(name = "éè´æºè½ä½") |
| | | @RestController |
| | | @RequestMapping("/purchase-ai") |
| | | public class PurchaseAiController extends BaseController { |
| | | |
| | | private static final String PURCHASE_FILE_ANALYZE_MEMORY_PREFIX = "purchase-file-analyze::"; |
| | | private static final int MAX_FILE_COUNT = 10; |
| | | private static final int MAX_SINGLE_FILE_TEXT_LENGTH = 8000; |
| | | private static final int MAX_TOTAL_FILE_TEXT_LENGTH = 30000; |
| | | private final PurchaseAiService purchaseAiService; |
| | | |
| | | private final PurchaseAgent purchaseAgent; |
| | | private final PurchaseIntentExecutor purchaseIntentExecutor; |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | private final MongoChatMemoryStore mongoChatMemoryStore; |
| | | private final AiChatSessionService aiChatSessionService; |
| | | private final AiFileTextExtractor aiFileTextExtractor; |
| | | private final ObjectMapper objectMapper; |
| | | private final IPurchaseLedgerService purchaseLedgerService; |
| | | private final IPaymentRegistrationService paymentRegistrationService; |
| | | private final PurchaseReturnOrdersService purchaseReturnOrdersService; |
| | | private final StorageBlobService storageBlobService; |
| | | private final SupplierManageMapper supplierManageMapper; |
| | | private final StreamingChatLanguageModel purchaseVisionStreamingChatModel; |
| | | |
| | | public PurchaseAiController(PurchaseAgent purchaseAgent, |
| | | PurchaseIntentExecutor purchaseIntentExecutor, |
| | | AiSessionUserContext aiSessionUserContext, |
| | | MongoChatMemoryStore mongoChatMemoryStore, |
| | | AiChatSessionService aiChatSessionService, |
| | | AiFileTextExtractor aiFileTextExtractor, |
| | | ObjectMapper objectMapper, |
| | | IPurchaseLedgerService purchaseLedgerService, |
| | | IPaymentRegistrationService paymentRegistrationService, |
| | | PurchaseReturnOrdersService purchaseReturnOrdersService, |
| | | StorageBlobService storageBlobService, |
| | | SupplierManageMapper supplierManageMapper, |
| | | @Qualifier("purchaseVisionStreamingChatModel") StreamingChatLanguageModel purchaseVisionStreamingChatModel) { |
| | | this.purchaseAgent = purchaseAgent; |
| | | this.purchaseIntentExecutor = purchaseIntentExecutor; |
| | | this.aiSessionUserContext = aiSessionUserContext; |
| | | this.mongoChatMemoryStore = mongoChatMemoryStore; |
| | | this.aiChatSessionService = aiChatSessionService; |
| | | this.aiFileTextExtractor = aiFileTextExtractor; |
| | | this.objectMapper = objectMapper; |
| | | this.purchaseLedgerService = purchaseLedgerService; |
| | | this.paymentRegistrationService = paymentRegistrationService; |
| | | this.purchaseReturnOrdersService = purchaseReturnOrdersService; |
| | | this.storageBlobService = storageBlobService; |
| | | this.supplierManageMapper = supplierManageMapper; |
| | | this.purchaseVisionStreamingChatModel = purchaseVisionStreamingChatModel; |
| | | public PurchaseAiController(PurchaseAiService purchaseAiService) { |
| | | this.purchaseAiService = purchaseAiService; |
| | | } |
| | | |
| | | @Operation(summary = "éè´å¯¹è¯") |
| | | @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8") |
| | | public Flux<String> chat(@RequestBody ChatForm chatForm) { |
| | | if (!StringUtils.hasText(chatForm.getMemoryId())) { |
| | | return Flux.just("memoryIdä¸è½ä¸ºç©º"); |
| | | } |
| | | if (!StringUtils.hasText(chatForm.getMessage())) { |
| | | return Flux.just("messageä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | String memoryId = chatForm.getMemoryId(); |
| | | String userMessage = chatForm.getMessage(); |
| | | |
| | | aiSessionUserContext.bind(memoryId, loginUser); |
| | | aiChatSessionService.touchSession(memoryId, loginUser, userMessage); |
| | | |
| | | String directResponse = purchaseIntentExecutor.tryExecute(memoryId, userMessage); |
| | | if (StringUtils.isNotEmpty(directResponse)) { |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(directResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(directResponse); |
| | | } |
| | | |
| | | return purchaseAgent.chat(memoryId, userMessage) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)); |
| | | return purchaseAiService.chat(chatForm, loginUser); |
| | | } |
| | | |
| | | @Operation(summary = "éè´å¤æä»¶åæ") |
| | |
| | | public Flux<String> analyzeFiles(@RequestParam("files") MultipartFile[] files, |
| | | @RequestParam(value = "message", required = false) String message, |
| | | @RequestParam(value = "memoryId", required = false) String memoryId) { |
| | | if (files == null || files.length == 0) { |
| | | return Flux.just("filesä¸è½ä¸ºç©º"); |
| | | } |
| | | if (files.length > MAX_FILE_COUNT) { |
| | | return Flux.just("䏿¬¡æå¤åæ" + MAX_FILE_COUNT + "个æä»¶"); |
| | | } |
| | | |
| | | String rawMemoryId = StringUtils.hasText(memoryId) ? memoryId : UUID.randomUUID().toString(); |
| | | String finalMemoryId = rawMemoryId.startsWith(PURCHASE_FILE_ANALYZE_MEMORY_PREFIX) |
| | | ? rawMemoryId |
| | | : PURCHASE_FILE_ANALYZE_MEMORY_PREFIX + rawMemoryId; |
| | | |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | aiSessionUserContext.bind(finalMemoryId, loginUser); |
| | | |
| | | String finalMessage = StringUtils.hasText(message) |
| | | ? message |
| | | : "请åæè¿äºéè´æä»¶ï¼æåå¯ç¨äºä¸å¡å¤ççæ°æ®ï¼å¹¶æ´çæå¾
客æ·ç¡®è®¤çæ ¼å¼"; |
| | | |
| | | List<String> filePaths; |
| | | try { |
| | | List<StorageBlobVO> uploadedFiles = storageBlobService.upload(copyFilesForUpload(files), true); |
| | | filePaths = resolveFileAccessPaths(uploadedFiles); |
| | | } catch (Exception ex) { |
| | | return Flux.just("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | try { |
| | | mongoChatMemoryStore.appendAnalyzeFileContext(finalMemoryId, finalMessage, filePaths); |
| | | } catch (Exception ex) { |
| | | return Flux.just("ä¼è¯æä»¶ä¿¡æ¯ä¿å失败"); |
| | | } |
| | | |
| | | String fileContent; |
| | | try { |
| | | fileContent = buildMultiFileContent(files); |
| | | } catch (IllegalArgumentException ex) { |
| | | return Flux.just(ex.getMessage()); |
| | | } catch (IOException ex) { |
| | | return Flux.just("æä»¶è¯»å失败"); |
| | | } |
| | | |
| | | if (!StringUtils.hasText(fileContent)) { |
| | | return Flux.just("æªæåå°æææä»¶å
容"); |
| | | } |
| | | |
| | | String userPrompt = buildPurchaseFileAnalyzePrompt(finalMessage, fileContent); |
| | | aiChatSessionService.touchSession(finalMemoryId, loginUser, "éè´å¤æä»¶åæ: " + finalMessage); |
| | | |
| | | if (containsImageFile(files)) { |
| | | return chatWithPurchaseVisionModel(finalMemoryId, finalMessage, userPrompt, files) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)); |
| | | } |
| | | |
| | | return Flux.defer(() -> purchaseAgent.chat(finalMemoryId, userPrompt)) |
| | | .onErrorResume(NoSuchElementException.class, ex -> { |
| | | mongoChatMemoryStore.deleteMessages(finalMemoryId); |
| | | return purchaseAgent.chat(finalMemoryId, userPrompt); |
| | | }) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)); |
| | | return purchaseAiService.analyzeFiles(files, message, memoryId, loginUser); |
| | | } |
| | | |
| | | @Operation(summary = "éè´å¤æä»¶åæç¡®è®¤å¤ç") |
| | | @PostMapping("/analyze-files/confirm") |
| | | public AjaxResult confirmAnalyzeResult(@RequestBody PurchaseAiConfirmRequest request) { |
| | | if (request == null || !StringUtils.hasText(request.getBusinessType())) { |
| | | return AjaxResult.error("businessTypeä¸è½ä¸ºç©º"); |
| | | } |
| | | if (request.getPayload() == null || request.getPayload().isEmpty()) { |
| | | return AjaxResult.error("payloadä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | try { |
| | | String businessType = request.getBusinessType().trim(); |
| | | return switch (businessType) { |
| | | case "purchase_ledger" -> processPurchaseLedger(request.getPayload()); |
| | | case "payment_registration" -> processPaymentRegistration(request.getPayload()); |
| | | case "purchase_return_order" -> processPurchaseReturnOrder(request.getPayload()); |
| | | default -> AjaxResult.error("æä¸æ¯æè¯¥ä¸å¡ç±»å: " + businessType); |
| | | }; |
| | | } catch (Exception ex) { |
| | | return AjaxResult.error(toCustomerMessage(ex)); |
| | | } |
| | | return purchaseAiService.confirmAnalyzeResult(request); |
| | | } |
| | | |
| | | @Operation(summary = "éè´ä¼è¯å表") |
| | | @GetMapping("/history/sessions") |
| | | public AjaxResult listSessions() { |
| | | return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser())); |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | return success(purchaseAiService.listSessions(loginUser)); |
| | | } |
| | | |
| | | @Operation(summary = "éè´ä¼è¯æ¶æ¯") |
| | | @GetMapping("/history/messages/{memoryId}") |
| | | public AjaxResult listMessages(@PathVariable String memoryId) { |
| | | return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser())); |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | return success(purchaseAiService.listMessages(memoryId, loginUser)); |
| | | } |
| | | |
| | | @Operation(summary = "å é¤éè´ä¼è¯") |
| | | @DeleteMapping("/history/{memoryId}") |
| | | public AjaxResult deleteSession(@PathVariable String memoryId) { |
| | | aiSessionUserContext.remove(memoryId); |
| | | return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | private String buildMultiFileContent(MultipartFile[] files) throws IOException { |
| | | StringBuilder builder = new StringBuilder(); |
| | | int totalLength = 0; |
| | | for (MultipartFile file : files) { |
| | | String text = aiFileTextExtractor.extractText(file); |
| | | if (!StringUtils.hasText(text)) { |
| | | continue; |
| | | } |
| | | String limitedText = text.length() > MAX_SINGLE_FILE_TEXT_LENGTH |
| | | ? text.substring(0, MAX_SINGLE_FILE_TEXT_LENGTH) |
| | | : text; |
| | | if (totalLength + limitedText.length() > MAX_TOTAL_FILE_TEXT_LENGTH) { |
| | | int remain = MAX_TOTAL_FILE_TEXT_LENGTH - totalLength; |
| | | if (remain <= 0) { |
| | | break; |
| | | } |
| | | limitedText = limitedText.substring(0, remain); |
| | | } |
| | | builder.append("\n--- æä»¶: ") |
| | | .append(file.getOriginalFilename()) |
| | | .append(" ---\n") |
| | | .append(limitedText) |
| | | .append('\n'); |
| | | totalLength += limitedText.length(); |
| | | } |
| | | return builder.toString(); |
| | | } |
| | | |
| | | private boolean containsImageFile(MultipartFile[] files) { |
| | | for (MultipartFile file : files) { |
| | | if (aiFileTextExtractor.isImageFile(file)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private List<String> resolveFileAccessPaths(List<StorageBlobVO> uploadedFiles) { |
| | | if (StringUtils.isEmpty(uploadedFiles)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | List<String> filePaths = new ArrayList<>(); |
| | | for (StorageBlobVO uploadedFile : uploadedFiles) { |
| | | if (uploadedFile == null) { |
| | | continue; |
| | | } |
| | | String selectedPath; |
| | | if (shouldUsePreviewPath(uploadedFile)) { |
| | | selectedPath = StringUtils.hasText(uploadedFile.getPreviewURL()) |
| | | ? uploadedFile.getPreviewURL() |
| | | : uploadedFile.getDownloadURL(); |
| | | } else { |
| | | selectedPath = StringUtils.hasText(uploadedFile.getDownloadURL()) |
| | | ? uploadedFile.getDownloadURL() |
| | | : uploadedFile.getPreviewURL(); |
| | | } |
| | | if (StringUtils.hasText(selectedPath)) { |
| | | filePaths.add(selectedPath); |
| | | } |
| | | } |
| | | return filePaths; |
| | | } |
| | | |
| | | private boolean shouldUsePreviewPath(StorageBlobVO uploadedFile) { |
| | | String contentType = uploadedFile.getContentType(); |
| | | if (StringUtils.hasText(contentType)) { |
| | | String normalized = contentType.toLowerCase(Locale.ROOT); |
| | | if (normalized.startsWith("image/") || "application/pdf".equals(normalized)) { |
| | | return true; |
| | | } |
| | | } |
| | | String filename = uploadedFile.getOriginalFilename(); |
| | | if (!StringUtils.hasText(filename) || !filename.contains(".")) { |
| | | return false; |
| | | } |
| | | String ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(Locale.ROOT); |
| | | return StringUtils.inStringIgnoreCase(ext, "png", "jpg", "jpeg", "webp", "bmp", "pdf"); |
| | | } |
| | | |
| | | private List<MultipartFile> copyFilesForUpload(MultipartFile[] files) throws IOException { |
| | | List<MultipartFile> copies = new ArrayList<>(); |
| | | for (MultipartFile file : files) { |
| | | copies.add(new InMemoryMultipartFile( |
| | | file.getName(), |
| | | file.getOriginalFilename(), |
| | | file.getContentType(), |
| | | file.getBytes() |
| | | )); |
| | | } |
| | | return copies; |
| | | } |
| | | |
| | | private static final class InMemoryMultipartFile implements MultipartFile { |
| | | private final String name; |
| | | private final String originalFilename; |
| | | private final String contentType; |
| | | private final byte[] bytes; |
| | | |
| | | private InMemoryMultipartFile(String name, String originalFilename, String contentType, byte[] bytes) { |
| | | this.name = name; |
| | | this.originalFilename = originalFilename; |
| | | this.contentType = contentType; |
| | | this.bytes = bytes == null ? new byte[0] : bytes; |
| | | } |
| | | |
| | | @Override |
| | | public String getName() { |
| | | return name; |
| | | } |
| | | |
| | | @Override |
| | | public String getOriginalFilename() { |
| | | return originalFilename; |
| | | } |
| | | |
| | | @Override |
| | | public String getContentType() { |
| | | return contentType; |
| | | } |
| | | |
| | | @Override |
| | | public boolean isEmpty() { |
| | | return bytes.length == 0; |
| | | } |
| | | |
| | | @Override |
| | | public long getSize() { |
| | | return bytes.length; |
| | | } |
| | | |
| | | @Override |
| | | public byte[] getBytes() { |
| | | return bytes.clone(); |
| | | } |
| | | |
| | | @Override |
| | | public InputStream getInputStream() { |
| | | return new ByteArrayInputStream(bytes); |
| | | } |
| | | |
| | | @Override |
| | | public void transferTo(File dest) throws IOException, IllegalStateException { |
| | | Files.write(dest.toPath(), bytes); |
| | | } |
| | | } |
| | | |
| | | private Flux<String> chatWithPurchaseVisionModel(String memoryId, |
| | | String userMessage, |
| | | String userPrompt, |
| | | MultipartFile[] files) { |
| | | return Flux.create(sink -> { |
| | | StringBuilder assistantReply = new StringBuilder(); |
| | | try { |
| | | List<Content> contents = new ArrayList<>(); |
| | | contents.add(TextContent.from(userPrompt)); |
| | | for (MultipartFile file : files) { |
| | | if (!aiFileTextExtractor.isImageFile(file)) { |
| | | continue; |
| | | } |
| | | contents.add(TextContent.from("ä¸é¢è¿å¼ å¾çæä»¶åï¼" + file.getOriginalFilename())); |
| | | contents.add(ImageContent.from(Image.builder() |
| | | .base64Data(Base64.getEncoder().encodeToString(file.getBytes())) |
| | | .mimeType(resolveImageMimeType(file)) |
| | | .build())); |
| | | } |
| | | |
| | | List<ChatMessage> messages = List.of( |
| | | SystemMessage.from("ä½ æ¯éè´ä¸å¡æä»¶åæå©æãè¯·ä»ææ¬åå¾çä¸è¯å«éè´å°è´¦ãéè´äº§åæç»ã仿¬¾æéè´§ä¿¡æ¯ï¼åªè¾åºåæ³ JSONã"), |
| | | UserMessage.from(contents) |
| | | ); |
| | | safeAppendMessages(memoryId, List.of(UserMessage.from("éè´å¤æä»¶åæ: " + userMessage))); |
| | | purchaseVisionStreamingChatModel.chat(messages, new StreamingChatResponseHandler() { |
| | | @Override |
| | | public void onPartialResponse(String partialResponse) { |
| | | if (partialResponse != null) { |
| | | assistantReply.append(partialResponse); |
| | | sink.next(partialResponse); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onCompleteResponse(ChatResponse completeResponse) { |
| | | if (StringUtils.hasText(assistantReply.toString())) { |
| | | safeAppendMessages(memoryId, List.of(AiMessage.from(assistantReply.toString()))); |
| | | } |
| | | sink.complete(); |
| | | } |
| | | |
| | | @Override |
| | | public void onError(Throwable error) { |
| | | sink.error(error); |
| | | } |
| | | }); |
| | | } catch (Exception ex) { |
| | | sink.next("å¾çæä»¶è¯»å失败ï¼è¯·ç¡®è®¤å¾çæ ¼å¼ä¸º pngãjpgãjpegãwebp æ bmpï¼ä¸å¤§å°ä¸è¶
è¿10MB"); |
| | | sink.complete(); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | private void safeAppendMessages(String memoryId, List<ChatMessage> messages) { |
| | | if (!StringUtils.hasText(memoryId) || StringUtils.isEmpty(messages)) { |
| | | return; |
| | | } |
| | | try { |
| | | mongoChatMemoryStore.appendMessages(memoryId, messages); |
| | | } catch (Exception ignored) { |
| | | } |
| | | } |
| | | |
| | | private String resolveImageMimeType(MultipartFile file) { |
| | | String contentType = file.getContentType(); |
| | | if (StringUtils.hasText(contentType) && contentType.startsWith("image/")) { |
| | | return contentType; |
| | | } |
| | | String filename = file.getOriginalFilename(); |
| | | String ext = ""; |
| | | if (StringUtils.hasText(filename) && filename.contains(".")) { |
| | | ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); |
| | | } |
| | | return switch (ext) { |
| | | case "jpg", "jpeg" -> "image/jpeg"; |
| | | case "webp" -> "image/webp"; |
| | | case "bmp" -> "image/bmp"; |
| | | default -> "image/png"; |
| | | }; |
| | | } |
| | | |
| | | private String buildPurchaseFileAnalyzePrompt(String message, String fileContent) { |
| | | return """ |
| | | ä½ æ¯éè´ä¸å¡æä»¶åæå©æãè¯·ä¸¥æ ¼æ ¹æ®ç¨æ·ä¸ä¼ çå¤ä¸ªæä»¶åç¨æ·è¦æ±æåéè´ä¸å¡æ°æ®ã |
| | | |
| | | ç¨æ·è¦æ±: |
| | | %s |
| | | |
| | | è¾åºè¦æ±: |
| | | 1. åªè¾åºåæ³ JSONï¼ä¸è¦ Markdownï¼ä¸è¦é¢å¤è§£éã |
| | | 2. JSON é¡¶å±å段åºå®ä¸º: |
| | | - success: boolean |
| | | - businessType: purchase_ledger | payment_registration | purchase_return_order | unknown |
| | | - action: confirm_required |
| | | - description: ä¸æè¯´æ |
| | | - confidence: 0å°1çå°æ° |
| | | - missingFields: ç¼ºå¤±åæ®µä¸æåç§°æ°ç»ï¼é¢å客æ·å±ç¤ºï¼ä¸è¦è¾åºè±æå段å |
| | | - warnings: é£é©æç¤ºæ°ç» |
| | | - payload: å¾
客æ·ç¡®è®¤çæ°æ®ï¼å段åå¿
须使ç¨å端 DTO åæ®µå |
| | | - preview: ç»å®¢æ·ç¡®è®¤ç¨ç䏿æè¦æ°ç» |
| | | 3. 妿å¯å¤æä¸ºéè´å°è´¦ï¼businessType ä½¿ç¨ purchase_ledgerï¼payload.purchaseLedgers 为éè´è®¢å/éè´å°è´¦æ°ç»: |
| | | - purchaseLedgers: éè´è®¢å/éè´å°è´¦æ°ç»ï¼æ¯æ¡è®°å½å段åå¿
é¡»ä¸ PurchaseLedgerDto ä¿æä¸è´ |
| | | - 产åæç»å¿
é¡»æ¾å¨æ¯æ¡éè´å°è´¦è®°å½ç productData åæ®µä¸ï¼productData ç±»å为 List<SalesLedgerProduct> |
| | | - ä¸è¦ä¼å
ä½¿ç¨ payload é¡¶å± productDataï¼é¡¶å± productData ä»
ä½ä¸ºæ§æ ¼å¼å
¼å®¹ |
| | | - æä»¶éçâéè´åå·âå°±æ¯âéè´ååå·âï¼ç»ä¸æ å°ä¸º purchaseContractNumber |
| | | - æä»¶éçâéå®åå·âå°±æ¯âéå®ååå·âï¼ç»ä¸æ å°ä¸º salesContractNo |
| | | - æææ¥æå段å¿
é¡»ä½¿ç¨ yyyy-MM-ddï¼ä¾å¦ 2026-04-30ï¼ä¸è¦è¾åº 4/30/26ã2026/4/30ã2026å¹´4æ30æ¥ æå¸¦æ¶åç§çæ ¼å¼ |
| | | - éè´å°è´¦ä¸éè¦å¨ payload ä¸ä¼ 审æ¹äººï¼ä¸è¦è¾åº approveUserIdsãapproverId |
| | | - missingFields åªå¡«åä¸å¡å¿
填使 æ³è¯å«çåæ®µï¼ä¸è¦æ PurchaseLedgerDto çææç©ºåæ®µé½å为缺失ï¼ç¼ºå¤±é¡¹å¿
é¡»å䏿ï¼ä¾å¦âä¾åºååç§°ââå«ç¨åä»·âï¼ä¸è¦å supplierIdãtaxInclusiveUnitPrice |
| | | - éè´å°è´¦ä¸»è¡¨å¿
å¡«åæ®µä»
æè¿äºå¤æ: purchaseContractNumberãsupplierName æ supplierId |
| | | - productData æ¯æ¡äº§åå¿
å¡«åæ®µ: productCategoryãspecificationModelãunitãquantityãtaxInclusiveUnitPrice æ taxInclusiveTotalPriceï¼å¦æåªæå«ç¨æ»ä»·åæ°éï¼å¿
é¡»è®¡ç® taxInclusiveUnitPriceï¼å¦æåªæå«ç¨åä»·åæ°éï¼å¿
é¡»è®¡ç® taxInclusiveTotalPrice |
| | | - 产ååæ®µæéè´å¯¼å
¥æ¥å£ PurchaseLedgerProductImportDto 对é½: éè´åå·ã产å大类ãè§æ ¼åå·ãåä½ãæ°éãç¨çãå«ç¨åä»·ãå«ç¨æ»ä»·ãå票类åãæ¯å¦è´¨æ£ |
| | | - éè´äº§å type åºå®ä¸º 2 |
| | | - purchaseLedgers æ¯æ¡è®°å½åªä½¿ç¨è¿äº PurchaseLedgerDto åæ®µå: |
| | | entryDateStart, entryDateEnd, id, purchaseContractNumber, supplierId, supplierName, isWhite, recorderId, recorderName, salesContractNo, salesContractNoId, projectName, entryDate, executionDate, remarks, attachmentMaterials, createdAt, updatedAt, salesLedgerId, hasChildren, Type, productData, tempFileIds, SalesLedgerFiles, phoneNumber, businessPersonId, productId, productModelId, invoiceNumber, invoiceAmount, ticketRegistrationId, contractAmount, receiptPaymentAmount, unReceiptPaymentAmount, type, paymentMethod, approvalStatus, templateName |
| | | - productData æ¯æ¡äº§ååªä½¿ç¨è¿äº SalesLedgerProduct åæ®µå: |
| | | productCategory, specificationModel, unit, quantity, taxRate, taxInclusiveUnitPrice, taxInclusiveTotalPrice, taxExclusiveTotalPrice, invoiceType, productId, productModelId, isChecked, type |
| | | 4. 妿å¯å¤æä¸ºä»æ¬¾ç»è®°ï¼businessType ä½¿ç¨ payment_registrationï¼payload.records ä¸ºä»æ¬¾ç»è®°æ°ç»ï¼å段尽éå
å« purchaseLedgerIdãsalesLedgerProductIdãcurrentPaymentAmountãpaymentMethodãpaymentDateã |
| | | 5. 妿å¯å¤æä¸ºéè´éè´§ï¼businessType ä½¿ç¨ purchase_return_orderï¼payload æ PurchaseReturnOrderDto ç»ç»ï¼æç»æ¾ purchaseReturnOrderProductsDtosã |
| | | 6. 缺å°ä¸å¡å¤çå¿
须忮µæ¶ï¼ä¸è¦ç¼é IDï¼æåæ®µæ¾å
¥ missingFieldsï¼å¹¶ä»è¿åå¯ç¡®è®¤çèç¨¿æ°æ®ã |
| | | 7. ææä¸æå
å®¹ç´æ¥ä¿çï¼ä¸è¦è½¬ä¹æ Unicodeã |
| | | |
| | | æä»¶å
容: |
| | | %s |
| | | """.formatted(message, fileContent); |
| | | } |
| | | |
| | | private AjaxResult processPurchaseLedger(Map<String, Object> payload) throws Exception { |
| | | if (payload.containsKey("purchaseLedgers")) { |
| | | return processPurchaseLedgerBatch(payload); |
| | | } |
| | | |
| | | Map<String, Object> normalizedPayload = normalizePurchaseLedgerMap(payload); |
| | | PurchaseLedgerDto dto = objectMapper.convertValue(normalizedPayload, PurchaseLedgerDto.class); |
| | | AjaxResult ledgerResult = validatePurchaseLedger(dto, 0); |
| | | if (ledgerResult != null) { |
| | | return ledgerResult; |
| | | } |
| | | AjaxResult supplierResult = fillSupplierIdByName(dto); |
| | | if (supplierResult != null) { |
| | | return supplierResult; |
| | | } |
| | | AjaxResult productResult = validatePurchaseProducts(dto.getProductData(), 0); |
| | | if (productResult != null) { |
| | | return productResult; |
| | | } |
| | | int result = purchaseLedgerService.addOrEditPurchase(dto); |
| | | return AjaxResult.success("éè´å°è´¦å·²å¤ç", result); |
| | | } |
| | | |
| | | private AjaxResult processPurchaseLedgerBatch(Map<String, Object> payload) throws Exception { |
| | | List<Map<String, Object>> purchaseLedgers = toMapList(payload.get("purchaseLedgers")); |
| | | if (purchaseLedgers.isEmpty()) { |
| | | return AjaxResult.error("purchaseLedgersä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | List<Map<String, Object>> topLevelProductData = toMapList(payload.get("productData")); |
| | | List<Map<String, Object>> results = new ArrayList<>(); |
| | | for (int i = 0; i < purchaseLedgers.size(); i++) { |
| | | Map<String, Object> ledgerMap = normalizePurchaseLedgerMap(purchaseLedgers.get(i)); |
| | | PurchaseLedgerDto dto = objectMapper.convertValue(ledgerMap, PurchaseLedgerDto.class); |
| | | AjaxResult ledgerResult = validatePurchaseLedger(dto, i); |
| | | if (ledgerResult != null) { |
| | | return ledgerResult; |
| | | } |
| | | AjaxResult supplierResult = fillSupplierIdByName(dto); |
| | | if (supplierResult != null) { |
| | | return supplierResult; |
| | | } |
| | | |
| | | List<SalesLedgerProduct> products = dto.getProductData(); |
| | | if (products == null || products.isEmpty()) { |
| | | products = matchProductsForLedger(ledgerMap, dto, topLevelProductData, purchaseLedgers.size() == 1); |
| | | dto.setProductData(products); |
| | | } |
| | | AjaxResult productResult = validatePurchaseProducts(products, i); |
| | | if (productResult != null) { |
| | | return productResult; |
| | | } |
| | | int result = purchaseLedgerService.addOrEditPurchase(dto); |
| | | |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("index", i); |
| | | item.put("purchaseContractNumber", dto.getPurchaseContractNumber()); |
| | | item.put("supplierId", dto.getSupplierId()); |
| | | item.put("supplierName", dto.getSupplierName()); |
| | | item.put("productCount", products.size()); |
| | | item.put("result", result); |
| | | results.add(item); |
| | | } |
| | | return AjaxResult.success("éè´å°è´¦å·²æ¹éå¤ç", results); |
| | | } |
| | | |
| | | private List<SalesLedgerProduct> matchProductsForLedger(Map<String, Object> ledgerMap, |
| | | PurchaseLedgerDto dto, |
| | | List<Map<String, Object>> productData, |
| | | boolean onlyOneLedger) { |
| | | List<SalesLedgerProduct> products = new ArrayList<>(); |
| | | for (Map<String, Object> productMap : productData) { |
| | | if (onlyOneLedger || productBelongsToLedger(productMap, ledgerMap, dto)) { |
| | | products.add(objectMapper.convertValue(normalizeSalesLedgerProductMap(productMap), SalesLedgerProduct.class)); |
| | | } |
| | | } |
| | | return products; |
| | | } |
| | | |
| | | private boolean productBelongsToLedger(Map<String, Object> productMap, Map<String, Object> ledgerMap, PurchaseLedgerDto dto) { |
| | | Long productPurchaseLedgerId = longValue(productMap, "purchaseLedgerId", "purchaseId", "éè´è®¢åid", "éè´å°è´¦id"); |
| | | if (productPurchaseLedgerId != null && dto.getId() != null && productPurchaseLedgerId.equals(dto.getId())) { |
| | | return true; |
| | | } |
| | | |
| | | Long productSalesLedgerId = longValue(productMap, "salesLedgerId"); |
| | | if (productSalesLedgerId != null && dto.getId() != null && productSalesLedgerId.equals(dto.getId())) { |
| | | return true; |
| | | } |
| | | |
| | | String productContractNo = stringValue(productMap, "purchaseContractNumber", "purchaseContractNo", "éè´ååå·", "éè´åå·", "éè´è®¢åå·"); |
| | | if (StringUtils.hasText(productContractNo) |
| | | && StringUtils.hasText(dto.getPurchaseContractNumber()) |
| | | && productContractNo.trim().equals(dto.getPurchaseContractNumber().trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String ledgerContractNo = stringValue(ledgerMap, "purchaseContractNumber", "purchaseContractNo", "éè´ååå·", "éè´åå·", "éè´è®¢åå·"); |
| | | if (StringUtils.hasText(productContractNo) |
| | | && StringUtils.hasText(ledgerContractNo) |
| | | && productContractNo.trim().equals(ledgerContractNo.trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String productSalesContractNo = stringValue(productMap, "salesContractNo", "salesContractNumber", "éå®ååå·", "éå®åå·", "éå®è®¢åå·"); |
| | | if (StringUtils.hasText(productSalesContractNo) |
| | | && StringUtils.hasText(dto.getSalesContractNo()) |
| | | && productSalesContractNo.trim().equals(dto.getSalesContractNo().trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String ledgerSalesContractNo = stringValue(ledgerMap, "salesContractNo", "salesContractNumber", "éå®ååå·", "éå®åå·", "éå®è®¢åå·"); |
| | | if (StringUtils.hasText(productSalesContractNo) |
| | | && StringUtils.hasText(ledgerSalesContractNo) |
| | | && productSalesContractNo.trim().equals(ledgerSalesContractNo.trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String productSupplierName = stringValue(productMap, "supplierName", "ä¾åºååç§°"); |
| | | return StringUtils.hasText(productSupplierName) |
| | | && StringUtils.hasText(dto.getSupplierName()) |
| | | && productSupplierName.trim().equals(dto.getSupplierName().trim()); |
| | | } |
| | | |
| | | private Map<String, Object> normalizePurchaseLedgerMap(Map<String, Object> source) { |
| | | Map<String, Object> target = new LinkedHashMap<>(); |
| | | copyPurchaseLedgerDtoFields(source, target); |
| | | putDtoFieldIfPresent(source, target, "entryDateStart", "å½å
¥å¼å§æ¥æ", "å½å
¥æ¥æå¼å§"); |
| | | putDtoFieldIfPresent(source, target, "entryDateEnd", "å½å
¥ç»ææ¥æ", "å½å
¥æ¥æç»æ"); |
| | | putDtoFieldIfPresent(source, target, "id", "éè´å°è´¦id", "éè´è®¢åid", "主é®"); |
| | | putDtoFieldIfPresent(source, target, "purchaseContractNumber", "purchaseContractNo", "éè´ååå·", "éè´åå·", "éè´è®¢åå·"); |
| | | putDtoFieldIfPresent(source, target, "supplierId", "ä¾åºåid", "ä¾åºåID", "ä¾åºååç§°id", "ä¾åºååç§°ID"); |
| | | putDtoFieldIfPresent(source, target, "supplierName", "ä¾åºå", "ä¾åºååç§°"); |
| | | putDtoFieldIfPresent(source, target, "isWhite", "æ¯å¦ç½åå"); |
| | | putDtoFieldIfPresent(source, target, "recorderId", "å½å
¥äººid", "å½å
¥äººID", "å½å
¥äººå§åid", "å½å
¥äººå§åID"); |
| | | putDtoFieldIfPresent(source, target, "recorderName", "å½å
¥äºº", "å½å
¥äººå§å"); |
| | | putDtoFieldIfPresent(source, target, "salesContractNo", "salesContractNumber", "éå®ååå·", "éå®åå·", "éå®è®¢åå·"); |
| | | putDtoFieldIfPresent(source, target, "salesContractNoId", "éå®ååå·id", "éå®ååå·ID", "éå®åå·id", "éå®åå·ID"); |
| | | putDtoFieldIfPresent(source, target, "projectName", "项ç®", "项ç®åç§°"); |
| | | putDtoFieldIfPresent(source, target, "entryDate", "å½å
¥æ¥æ"); |
| | | putDtoFieldIfPresent(source, target, "executionDate", "ç¾è®¢æ¥æ", "ååç¾è®¢æ¥æ"); |
| | | putDtoFieldIfPresent(source, target, "remarks", "夿³¨", "说æ"); |
| | | putDtoFieldIfPresent(source, target, "attachmentMaterials", "éä»¶ææ", "éä»¶ææè·¯å¾æåç§°"); |
| | | putDtoFieldIfPresent(source, target, "createdAt", "å建æ¶é´", "è®°å½å建æ¶é´"); |
| | | putDtoFieldIfPresent(source, target, "updatedAt", "æ´æ°æ¶é´", "è®°å½æåæ´æ°æ¶é´"); |
| | | putDtoFieldIfPresent(source, target, "salesLedgerId", "éå®å°è´¦id", "éå®å°è´¦ID", "å
³èéå®å°è´¦ä¸»è¡¨ä¸»é®"); |
| | | putDtoFieldIfPresent(source, target, "hasChildren", "æ¯å¦æå级", "æ¯å¦ææç»"); |
| | | putDtoFieldIfPresent(source, target, "Type", "å°è´¦ç±»å", "ä¸å¡ç±»å"); |
| | | putDtoFieldIfPresent(source, target, "productData", "products", "产åæç»", "éè´äº§åæç»"); |
| | | putDtoFieldIfPresent(source, target, "tempFileIds", "ä¸´æ¶æä»¶id", "ä¸´æ¶æä»¶ID", "ä¸´æ¶æä»¶ids"); |
| | | putDtoFieldIfPresent(source, target, "SalesLedgerFiles", "éä»¶å表", "éå®å°è´¦éä»¶"); |
| | | putDtoFieldIfPresent(source, target, "phoneNumber", "ä¸å¡åææºå·", "ææºå·"); |
| | | putDtoFieldIfPresent(source, target, "businessPersonId", "ä¸å¡åid", "ä¸å¡åID"); |
| | | putDtoFieldIfPresent(source, target, "productId", "产åid", "产åID"); |
| | | putDtoFieldIfPresent(source, target, "productModelId", "产åè§æ ¼id", "产åè§æ ¼ID"); |
| | | putDtoFieldIfPresent(source, target, "invoiceNumber", "å票å·", "å票å·ç "); |
| | | putDtoFieldIfPresent(source, target, "invoiceAmount", "å票éé¢", "å票éé¢ï¼å
ï¼"); |
| | | putDtoFieldIfPresent(source, target, "ticketRegistrationId", "æ¥ç¥¨ç»è®°id", "æ¥ç¥¨ç»è®°ID"); |
| | | putDtoFieldIfPresent(source, target, "contractAmount", "ååéé¢", "ååéé¢ï¼äº§åå«ç¨æ»ä»·ï¼"); |
| | | putDtoFieldIfPresent(source, target, "receiptPaymentAmount", "æ¥ç¥¨éé¢", "å·²æ¥ç¥¨éé¢", "å·²æ¥ç¥¨éé¢(å
)"); |
| | | putDtoFieldIfPresent(source, target, "unReceiptPaymentAmount", "æªæ¥ç¥¨éé¢", "æªæ¥ç¥¨éé¢(å
)"); |
| | | putDtoFieldIfPresent(source, target, "type", "æä»¶ç±»å"); |
| | | putDtoFieldIfPresent(source, target, "paymentMethod", "仿¬¾æ¹å¼"); |
| | | putDtoFieldIfPresent(source, target, "approvalStatus", "审æ¹ç¶æ"); |
| | | putDtoFieldIfPresent(source, target, "templateName", "模æ¿åç§°"); |
| | | target.remove("approveUserIds"); |
| | | target.remove("approverId"); |
| | | normalizeNestedProductData(target); |
| | | attachImportStyleProductData(source, target); |
| | | if (target.get("type") == null) { |
| | | target.put("type", 2); |
| | | } |
| | | target.putIfAbsent("entryDate", LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)); |
| | | normalizePurchaseLedgerDateFields(target); |
| | | return target; |
| | | } |
| | | |
| | | private void attachImportStyleProductData(Map<String, Object> source, Map<String, Object> target) { |
| | | if (target.get("productData") != null) { |
| | | return; |
| | | } |
| | | Map<String, Object> productMap = normalizeSalesLedgerProductMap(source); |
| | | if (hasImportStyleProductData(productMap)) { |
| | | target.put("productData", List.of(productMap)); |
| | | } |
| | | } |
| | | |
| | | private boolean hasImportStyleProductData(Map<String, Object> productMap) { |
| | | return hasMapText(productMap, "productCategory") |
| | | || hasMapText(productMap, "specificationModel") |
| | | || productMap.get("quantity") != null |
| | | || productMap.get("taxInclusiveUnitPrice") != null |
| | | || productMap.get("taxInclusiveTotalPrice") != null; |
| | | } |
| | | |
| | | private boolean hasMapText(Map<String, Object> map, String key) { |
| | | Object value = map.get(key); |
| | | return value != null && StringUtils.hasText(String.valueOf(value)); |
| | | } |
| | | |
| | | private void normalizeNestedProductData(Map<String, Object> target) { |
| | | Object productDataValue = target.get("productData"); |
| | | if (productDataValue == null) { |
| | | return; |
| | | } |
| | | List<Map<String, Object>> productMaps = toMapList(productDataValue); |
| | | List<Map<String, Object>> normalizedProducts = new ArrayList<>(); |
| | | for (Map<String, Object> productMap : productMaps) { |
| | | normalizedProducts.add(normalizeSalesLedgerProductMap(productMap)); |
| | | } |
| | | target.put("productData", normalizedProducts); |
| | | } |
| | | |
| | | private Map<String, Object> normalizeSalesLedgerProductMap(Map<String, Object> source) { |
| | | Map<String, Object> target = new LinkedHashMap<>(); |
| | | copySalesLedgerProductFields(source, target); |
| | | putDtoFieldIfPresent(source, target, "productCategory", "产å大类", "产ååç§°", "产å", "åå", "ç©æåç§°"); |
| | | putDtoFieldIfPresent(source, target, "specificationModel", "è§æ ¼åå·", "åå·", "è§æ ¼", "产åè§æ ¼"); |
| | | putDtoFieldIfPresent(source, target, "unit", "åä½"); |
| | | putDtoFieldIfPresent(source, target, "quantity", "æ°é", "éè´æ°é"); |
| | | putDtoFieldIfPresent(source, target, "taxRate", "ç¨ç"); |
| | | putDtoFieldIfPresent(source, target, "taxInclusiveUnitPrice", "å«ç¨åä»·", "åä»·", "éè´åä»·", "å«ç¨ä»·æ ¼"); |
| | | putDtoFieldIfPresent(source, target, "taxInclusiveTotalPrice", "å«ç¨æ»ä»·", "æ»ä»·", "éè´éé¢", "éé¢", "ååéé¢"); |
| | | putDtoFieldIfPresent(source, target, "taxExclusiveTotalPrice", "ä¸å«ç¨æ»ä»·"); |
| | | putDtoFieldIfPresent(source, target, "invoiceType", "å票类å", "å票类å«"); |
| | | putDtoFieldIfPresent(source, target, "productId", "产åid", "产åID"); |
| | | putDtoFieldIfPresent(source, target, "productModelId", "产åè§æ ¼id", "产åè§æ ¼ID", "åå·id", "åå·ID"); |
| | | putDtoFieldIfPresent(source, target, "isChecked", "æ¯å¦è´¨æ£", "æ¯å¦è´¨æ£éª", "è´¨æ£"); |
| | | putDtoFieldIfPresent(source, target, "type", "å°è´¦ç±»å"); |
| | | normalizeProductAmounts(target); |
| | | target.putIfAbsent("type", 2); |
| | | return target; |
| | | } |
| | | |
| | | private void copySalesLedgerProductFields(Map<String, Object> source, Map<String, Object> target) { |
| | | String[] productFields = { |
| | | "id", "salesLedgerId", "warnNum", "productCategory", "specificationModel", "unit", |
| | | "speculativeTradingName", "quantity", "minStock", "taxRate", "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", "taxExclusiveTotalPrice", "invoiceType", "type", "ticketsNum", |
| | | "ticketsAmount", "futureTickets", "futureTicketsAmount", "invoiceNum", "noInvoiceNum", |
| | | "invoiceAmount", "noInvoiceAmount", "productId", "productModelId", "register", "registerDate", |
| | | "approveStatus", "pendingInvoiceTotal", "invoiceTotal", "pendingTicketsTotal", "ticketsTotal", |
| | | "isChecked", "isProduction" |
| | | }; |
| | | for (String field : productFields) { |
| | | if (source.containsKey(field)) { |
| | | target.put(field, source.get(field)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void normalizeProductAmounts(Map<String, Object> target) { |
| | | BigDecimal quantity = decimalValue(target.get("quantity")); |
| | | BigDecimal unitPrice = decimalValue(target.get("taxInclusiveUnitPrice")); |
| | | BigDecimal totalPrice = decimalValue(target.get("taxInclusiveTotalPrice")); |
| | | if (unitPrice == null && totalPrice != null && quantity != null && quantity.compareTo(BigDecimal.ZERO) != 0) { |
| | | target.put("taxInclusiveUnitPrice", totalPrice.divide(quantity, 6, RoundingMode.HALF_UP)); |
| | | } |
| | | if (totalPrice == null && unitPrice != null && quantity != null) { |
| | | target.put("taxInclusiveTotalPrice", unitPrice.multiply(quantity)); |
| | | } |
| | | BigDecimal taxRate = decimalValue(target.get("taxRate")); |
| | | totalPrice = decimalValue(target.get("taxInclusiveTotalPrice")); |
| | | if (target.get("taxExclusiveTotalPrice") == null && totalPrice != null && taxRate != null) { |
| | | BigDecimal divisor = BigDecimal.ONE.add(taxRate.divide(new BigDecimal("100"), 6, RoundingMode.HALF_UP)); |
| | | target.put("taxExclusiveTotalPrice", totalPrice.divide(divisor, 2, RoundingMode.HALF_UP)); |
| | | } |
| | | } |
| | | |
| | | private AjaxResult validatePurchaseProducts(List<SalesLedgerProduct> products, int ledgerIndex) { |
| | | if (products == null || products.isEmpty()) { |
| | | return null; |
| | | } |
| | | for (int i = 0; i < products.size(); i++) { |
| | | SalesLedgerProduct product = products.get(i); |
| | | String prefix = "第" + (ledgerIndex + 1) + "个éè´å°è´¦ç第" + (i + 1) + "æ¡äº§å"; |
| | | if (!StringUtils.hasText(product.getProductCategory())) { |
| | | return AjaxResult.error(prefix + "缺å°äº§ååç§°ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (!StringUtils.hasText(product.getSpecificationModel())) { |
| | | return AjaxResult.error(prefix + "缺å°è§æ ¼åå·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (!StringUtils.hasText(product.getUnit())) { |
| | | return AjaxResult.error(prefix + "缺å°åä½ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (product.getQuantity() == null) { |
| | | return AjaxResult.error(prefix + "ç¼ºå°æ°é"); |
| | | } |
| | | if (product.getTaxInclusiveUnitPrice() == null) { |
| | | return AjaxResult.error(prefix + "缺å°å«ç¨åä»·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (product.getTaxInclusiveTotalPrice() == null) { |
| | | return AjaxResult.error(prefix + "缺å°å«ç¨æ»ä»·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private AjaxResult validatePurchaseLedger(PurchaseLedgerDto dto, int ledgerIndex) { |
| | | String prefix = "第" + (ledgerIndex + 1) + "个éè´å°è´¦"; |
| | | if (!StringUtils.hasText(dto.getPurchaseContractNumber())) { |
| | | return AjaxResult.error(prefix + "缺å°éè´ååå·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (dto.getSupplierId() == null && !StringUtils.hasText(dto.getSupplierName())) { |
| | | return AjaxResult.error(prefix + "缺å°ä¾åºååç§°ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private void normalizePurchaseLedgerDateFields(Map<String, Object> target) { |
| | | normalizeDateField(target, "entryDate"); |
| | | normalizeDateField(target, "executionDate"); |
| | | normalizeDateField(target, "createdAt"); |
| | | normalizeDateField(target, "updatedAt"); |
| | | } |
| | | |
| | | private void normalizeDateField(Map<String, Object> target, String fieldName) { |
| | | Object value = target.get(fieldName); |
| | | if (value == null) { |
| | | return; |
| | | } |
| | | String normalizedDate = normalizeDateValue(value); |
| | | if (StringUtils.hasText(normalizedDate)) { |
| | | target.put(fieldName, normalizedDate); |
| | | } |
| | | } |
| | | |
| | | private String normalizeDateValue(Object value) { |
| | | if (value instanceof Date date) { |
| | | return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE); |
| | | } |
| | | if (value instanceof Number number) { |
| | | return LocalDate.of(1899, 12, 30) |
| | | .plusDays(number.longValue()) |
| | | .format(DateTimeFormatter.ISO_LOCAL_DATE); |
| | | } |
| | | |
| | | String text = String.valueOf(value).trim(); |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | if (text.length() >= 10 && text.charAt(4) == '-' && text.charAt(7) == '-') { |
| | | return text.substring(0, 10); |
| | | } |
| | | |
| | | String normalizedText = text.replace("å¹´", "-") |
| | | .replace("æ", "-") |
| | | .replace("æ¥", "") |
| | | .replace(".", "-") |
| | | .replace("/", "-") |
| | | .trim(); |
| | | DateTimeFormatter[] formatters = { |
| | | DateTimeFormatter.ofPattern("yyyy-M-d"), |
| | | DateTimeFormatter.ofPattern("M-d-yyyy"), |
| | | DateTimeFormatter.ofPattern("M-d-yy") |
| | | }; |
| | | for (DateTimeFormatter formatter : formatters) { |
| | | try { |
| | | return LocalDate.parse(normalizedText, formatter).format(DateTimeFormatter.ISO_LOCAL_DATE); |
| | | } catch (DateTimeParseException ignored) { |
| | | // Try the next supported input pattern. |
| | | } |
| | | } |
| | | return text; |
| | | } |
| | | |
| | | private void copyPurchaseLedgerDtoFields(Map<String, Object> source, Map<String, Object> target) { |
| | | String[] dtoFields = { |
| | | "entryDateStart", "entryDateEnd", "id", "purchaseContractNumber", |
| | | "supplierId", "supplierName", "isWhite", "recorderId", "recorderName", "salesContractNo", |
| | | "salesContractNoId", "projectName", "entryDate", "executionDate", "remarks", "attachmentMaterials", |
| | | "createdAt", "updatedAt", "salesLedgerId", "hasChildren", "Type", "productData", "tempFileIds", |
| | | "SalesLedgerFiles", "phoneNumber", "businessPersonId", "productId", "productModelId", "invoiceNumber", |
| | | "invoiceAmount", "ticketRegistrationId", "contractAmount", "receiptPaymentAmount", |
| | | "unReceiptPaymentAmount", "type", "paymentMethod", "approvalStatus", "templateName" |
| | | }; |
| | | for (String field : dtoFields) { |
| | | if (source.containsKey(field)) { |
| | | target.put(field, source.get(field)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void putDtoFieldIfPresent(Map<String, Object> source, Map<String, Object> target, String dtoField, String... aliases) { |
| | | if (target.containsKey(dtoField) && target.get(dtoField) != null) { |
| | | return; |
| | | } |
| | | for (String alias : aliases) { |
| | | Object value = source.get(alias); |
| | | if (value != null && StringUtils.hasText(String.valueOf(value))) { |
| | | target.put(dtoField, value); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | private List<Map<String, Object>> toMapList(Object value) { |
| | | if (value == null) { |
| | | return List.of(); |
| | | } |
| | | return objectMapper.convertValue(value, new TypeReference<List<Map<String, Object>>>() { |
| | | }); |
| | | } |
| | | |
| | | private String stringValue(Map<String, Object> map, String... keys) { |
| | | for (String key : keys) { |
| | | Object value = map.get(key); |
| | | if (value != null && StringUtils.hasText(String.valueOf(value))) { |
| | | return String.valueOf(value); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Long longValue(Map<String, Object> map, String... keys) { |
| | | String value = stringValue(map, keys); |
| | | if (!StringUtils.hasText(value)) { |
| | | return null; |
| | | } |
| | | try { |
| | | return Long.parseLong(value.trim()); |
| | | } catch (NumberFormatException ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private BigDecimal decimalValue(Object value) { |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | if (value instanceof BigDecimal decimal) { |
| | | return decimal; |
| | | } |
| | | if (value instanceof Number number) { |
| | | return new BigDecimal(String.valueOf(number)); |
| | | } |
| | | String text = String.valueOf(value) |
| | | .replace(",", "") |
| | | .replace("ï¼", "") |
| | | .replace("å
", "") |
| | | .replace("ï¿¥", "") |
| | | .trim(); |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | try { |
| | | return new BigDecimal(text); |
| | | } catch (NumberFormatException ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private String toCustomerMessage(Exception ex) { |
| | | String message = ex.getMessage(); |
| | | if (!StringUtils.hasText(message)) { |
| | | return "å¤ç失败ï¼è¯·æ£æ¥ç¡®è®¤æ°æ®åéè¯"; |
| | | } |
| | | if (message.contains("tax_inclusive_unit_price")) { |
| | | return "å¤ç失败ï¼äº§åæç»ç¼ºå°å«ç¨åä»·ï¼è¯·è¡¥å
åå确认"; |
| | | } |
| | | if (message.contains("tax_inclusive_total_price")) { |
| | | return "å¤ç失败ï¼äº§åæç»ç¼ºå°å«ç¨æ»ä»·ï¼è¯·è¡¥å
åå确认"; |
| | | } |
| | | if (message.contains("entryDate")) { |
| | | return "å¤ç失败ï¼å½å
¥æ¥ææ ¼å¼ä¸æ£ç¡®ï¼è¯·ä½¿ç¨ yyyy-MM-ddï¼ä¾å¦ 2026-04-30"; |
| | | } |
| | | if (message.contains("supplier")) { |
| | | return "å¤ç失败ï¼ä¾åºåä¿¡æ¯ä¸å®æ´ï¼è¯·ç¡®è®¤ä¾åºååç§°æä¾åºåID"; |
| | | } |
| | | if (message.contains("SQL") || message.contains("java.") || message.contains("Exception")) { |
| | | return "å¤ç失败ï¼ç¡®è®¤æ°æ®ä¸å®æ´ææ ¼å¼ä¸æ£ç¡®ï¼è¯·æ£æ¥å¿
å¡«åæ®µåéè¯"; |
| | | } |
| | | return "å¤ç失败ï¼" + message; |
| | | } |
| | | |
| | | private AjaxResult fillSupplierIdByName(PurchaseLedgerDto dto) { |
| | | if (dto.getSupplierId() != null) { |
| | | return null; |
| | | } |
| | | if (!StringUtils.hasText(dto.getSupplierName())) { |
| | | return AjaxResult.error("ä¾åºåIDä¸è½ä¸ºç©ºï¼æªè¯å«å°ä¾åºååç§°ï¼æ æ³èªå¨å¹é
ä¾åºåID"); |
| | | } |
| | | |
| | | SupplierManage supplier = supplierManageMapper.selectOne(new LambdaQueryWrapper<SupplierManage>() |
| | | .eq(SupplierManage::getSupplierName, dto.getSupplierName().trim()) |
| | | .last("limit 1")); |
| | | if (supplier == null) { |
| | | return AjaxResult.error("æªæ¾å°ä¾åºåï¼" + dto.getSupplierName() + "ï¼è¯·å
ç»´æ¤ä¾åºåææå¨éæ©ä¾åºåID"); |
| | | } |
| | | dto.setSupplierId(supplier.getId()); |
| | | return null; |
| | | } |
| | | |
| | | private AjaxResult processPaymentRegistration(Map<String, Object> payload) { |
| | | Object recordsValue = payload.get("records"); |
| | | List<PaymentRegistration> records; |
| | | if (recordsValue == null) { |
| | | records = Collections.singletonList(objectMapper.convertValue(payload, PaymentRegistration.class)); |
| | | } else { |
| | | records = objectMapper.convertValue(recordsValue, new TypeReference<List<PaymentRegistration>>() { |
| | | }); |
| | | } |
| | | int result = paymentRegistrationService.insertPaymentRegistration(records); |
| | | return AjaxResult.success("仿¬¾ç»è®°å·²å¤ç", result); |
| | | } |
| | | |
| | | private AjaxResult processPurchaseReturnOrder(Map<String, Object> payload) { |
| | | PurchaseReturnOrderDto dto = objectMapper.convertValue(payload, PurchaseReturnOrderDto.class); |
| | | Boolean result = purchaseReturnOrdersService.add(dto); |
| | | return AjaxResult.success("éè´éè´§åå·²å¤ç", result); |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | return toAjax(purchaseAiService.deleteSession(memoryId, loginUser)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.ruoyi.ai.assistant.PurchaseAgent; |
| | | import com.ruoyi.ai.assistant.PurchaseIntentExecutor; |
| | | import com.ruoyi.ai.bean.ChatForm; |
| | | import com.ruoyi.ai.bean.PurchaseAiConfirmRequest; |
| | | import com.ruoyi.ai.context.AiSessionUserContext; |
| | | import com.ruoyi.ai.dto.AiChatMessageDto; |
| | | import com.ruoyi.ai.dto.AiChatSessionDto; |
| | | import com.ruoyi.ai.store.MongoChatMemoryStore; |
| | | import com.ruoyi.basic.dto.StorageBlobVO; |
| | | import com.ruoyi.basic.mapper.SupplierManageMapper; |
| | | import com.ruoyi.basic.pojo.SupplierManage; |
| | | import com.ruoyi.basic.service.StorageBlobService; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.framework.security.LoginUser; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import com.ruoyi.purchase.dto.PurchaseLedgerDto; |
| | | import com.ruoyi.purchase.dto.PurchaseReturnOrderDto; |
| | | import com.ruoyi.purchase.pojo.PaymentRegistration; |
| | | import com.ruoyi.purchase.service.IPaymentRegistrationService; |
| | | import com.ruoyi.purchase.service.IPurchaseLedgerService; |
| | | import com.ruoyi.purchase.service.PurchaseReturnOrdersService; |
| | | import com.ruoyi.sales.pojo.SalesLedgerProduct; |
| | | import dev.langchain4j.data.image.Image; |
| | | import dev.langchain4j.data.message.AiMessage; |
| | | import dev.langchain4j.data.message.ChatMessage; |
| | | import dev.langchain4j.data.message.Content; |
| | | import dev.langchain4j.data.message.ImageContent; |
| | | import dev.langchain4j.data.message.SystemMessage; |
| | | import dev.langchain4j.data.message.TextContent; |
| | | import dev.langchain4j.data.message.UserMessage; |
| | | import dev.langchain4j.model.chat.StreamingChatLanguageModel; |
| | | import dev.langchain4j.model.chat.response.ChatResponse; |
| | | import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; |
| | | import org.springframework.beans.factory.annotation.Qualifier; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.Base64; |
| | | import java.util.Arrays; |
| | | import java.time.LocalDate; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.format.DateTimeParseException; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.Map; |
| | | import java.util.NoSuchElementException; |
| | | import java.util.UUID; |
| | | import java.nio.file.Files; |
| | | |
| | | @Service |
| | | public class PurchaseAiService { |
| | | |
| | | private static final String PURCHASE_FILE_ANALYZE_MEMORY_PREFIX = "purchase-file-analyze::"; |
| | | private static final int MAX_FILE_COUNT = 10; |
| | | private static final int MAX_SINGLE_FILE_TEXT_LENGTH = 8000; |
| | | private static final int MAX_TOTAL_FILE_TEXT_LENGTH = 30000; |
| | | |
| | | private final PurchaseAgent purchaseAgent; |
| | | private final PurchaseIntentExecutor purchaseIntentExecutor; |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | private final MongoChatMemoryStore mongoChatMemoryStore; |
| | | private final AiChatSessionService aiChatSessionService; |
| | | private final AiFileTextExtractor aiFileTextExtractor; |
| | | private final ObjectMapper objectMapper; |
| | | private final IPurchaseLedgerService purchaseLedgerService; |
| | | private final IPaymentRegistrationService paymentRegistrationService; |
| | | private final PurchaseReturnOrdersService purchaseReturnOrdersService; |
| | | private final StorageBlobService storageBlobService; |
| | | private final SupplierManageMapper supplierManageMapper; |
| | | private final StreamingChatLanguageModel purchaseVisionStreamingChatModel; |
| | | |
| | | public PurchaseAiService(PurchaseAgent purchaseAgent, |
| | | PurchaseIntentExecutor purchaseIntentExecutor, |
| | | AiSessionUserContext aiSessionUserContext, |
| | | MongoChatMemoryStore mongoChatMemoryStore, |
| | | AiChatSessionService aiChatSessionService, |
| | | AiFileTextExtractor aiFileTextExtractor, |
| | | ObjectMapper objectMapper, |
| | | IPurchaseLedgerService purchaseLedgerService, |
| | | IPaymentRegistrationService paymentRegistrationService, |
| | | PurchaseReturnOrdersService purchaseReturnOrdersService, |
| | | StorageBlobService storageBlobService, |
| | | SupplierManageMapper supplierManageMapper, |
| | | @Qualifier("purchaseVisionStreamingChatModel") StreamingChatLanguageModel purchaseVisionStreamingChatModel) { |
| | | this.purchaseAgent = purchaseAgent; |
| | | this.purchaseIntentExecutor = purchaseIntentExecutor; |
| | | this.aiSessionUserContext = aiSessionUserContext; |
| | | this.mongoChatMemoryStore = mongoChatMemoryStore; |
| | | this.aiChatSessionService = aiChatSessionService; |
| | | this.aiFileTextExtractor = aiFileTextExtractor; |
| | | this.objectMapper = objectMapper; |
| | | this.purchaseLedgerService = purchaseLedgerService; |
| | | this.paymentRegistrationService = paymentRegistrationService; |
| | | this.purchaseReturnOrdersService = purchaseReturnOrdersService; |
| | | this.storageBlobService = storageBlobService; |
| | | this.supplierManageMapper = supplierManageMapper; |
| | | this.purchaseVisionStreamingChatModel = purchaseVisionStreamingChatModel; |
| | | } |
| | | |
| | | public Flux<String> chat(ChatForm chatForm, LoginUser loginUser) { |
| | | if (!StringUtils.hasText(chatForm.getMemoryId())) { |
| | | return Flux.just("memoryIdä¸è½ä¸ºç©º"); |
| | | } |
| | | if (!StringUtils.hasText(chatForm.getMessage())) { |
| | | return Flux.just("messageä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | String memoryId = chatForm.getMemoryId(); |
| | | String userMessage = chatForm.getMessage(); |
| | | |
| | | aiSessionUserContext.bind(memoryId, loginUser); |
| | | aiChatSessionService.touchSession(memoryId, loginUser, userMessage); |
| | | |
| | | String directResponse = purchaseIntentExecutor.tryExecute(memoryId, userMessage); |
| | | if (StringUtils.isNotEmpty(directResponse)) { |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(directResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(directResponse); |
| | | } |
| | | |
| | | return purchaseAgent.chat(memoryId, userMessage) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)); |
| | | } |
| | | |
| | | public Flux<String> analyzeFiles(MultipartFile[] files, |
| | | String message, |
| | | String memoryId, |
| | | LoginUser loginUser) { |
| | | if (files == null || files.length == 0) { |
| | | return Flux.just("filesä¸è½ä¸ºç©º"); |
| | | } |
| | | if (files.length > MAX_FILE_COUNT) { |
| | | return Flux.just("䏿¬¡æå¤åæ" + MAX_FILE_COUNT + "个æä»¶"); |
| | | } |
| | | |
| | | String rawMemoryId = StringUtils.hasText(memoryId) ? memoryId : UUID.randomUUID().toString(); |
| | | String finalMemoryId = rawMemoryId.startsWith(PURCHASE_FILE_ANALYZE_MEMORY_PREFIX) |
| | | ? rawMemoryId |
| | | : PURCHASE_FILE_ANALYZE_MEMORY_PREFIX + rawMemoryId; |
| | | |
| | | aiSessionUserContext.bind(finalMemoryId, loginUser); |
| | | |
| | | String finalMessage = StringUtils.hasText(message) |
| | | ? message |
| | | : "请åæè¿äºéè´æä»¶ï¼æåå¯ç¨äºä¸å¡å¤ççæ°æ®ï¼å¹¶æ´çæå¾
客æ·ç¡®è®¤çæ ¼å¼"; |
| | | |
| | | List<String> filePaths; |
| | | try { |
| | | List<StorageBlobVO> uploadedFiles = storageBlobService.upload(copyFilesForUpload(files), true); |
| | | filePaths = resolveFileAccessPaths(uploadedFiles); |
| | | } catch (Exception ex) { |
| | | return Flux.just("æä»¶ä¸ä¼ 失败"); |
| | | } |
| | | try { |
| | | mongoChatMemoryStore.appendAnalyzeFileContext(finalMemoryId, finalMessage, filePaths); |
| | | } catch (Exception ex) { |
| | | return Flux.just("ä¼è¯æä»¶ä¿¡æ¯ä¿å失败"); |
| | | } |
| | | |
| | | String fileContent; |
| | | try { |
| | | fileContent = buildMultiFileContent(files); |
| | | } catch (IllegalArgumentException ex) { |
| | | return Flux.just(ex.getMessage()); |
| | | } catch (IOException ex) { |
| | | return Flux.just("æä»¶è¯»å失败"); |
| | | } |
| | | |
| | | if (!StringUtils.hasText(fileContent)) { |
| | | return Flux.just("æªæåå°æææä»¶å
容"); |
| | | } |
| | | |
| | | String userPrompt = buildPurchaseFileAnalyzePrompt(finalMessage, fileContent); |
| | | aiChatSessionService.touchSession(finalMemoryId, loginUser, "éè´å¤æä»¶åæ: " + finalMessage); |
| | | |
| | | if (containsImageFile(files)) { |
| | | return chatWithPurchaseVisionModel(finalMemoryId, finalMessage, userPrompt, files) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)); |
| | | } |
| | | |
| | | return Flux.defer(() -> purchaseAgent.chat(finalMemoryId, userPrompt)) |
| | | .onErrorResume(NoSuchElementException.class, ex -> { |
| | | mongoChatMemoryStore.deleteMessages(finalMemoryId); |
| | | return purchaseAgent.chat(finalMemoryId, userPrompt); |
| | | }) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)); |
| | | } |
| | | |
| | | public AjaxResult confirmAnalyzeResult(PurchaseAiConfirmRequest request) { |
| | | if (request == null || !StringUtils.hasText(request.getBusinessType())) { |
| | | return AjaxResult.error("businessTypeä¸è½ä¸ºç©º"); |
| | | } |
| | | if (request.getPayload() == null || request.getPayload().isEmpty()) { |
| | | return AjaxResult.error("payloadä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | try { |
| | | String businessType = request.getBusinessType().trim(); |
| | | return switch (businessType) { |
| | | case "purchase_ledger" -> processPurchaseLedger(request.getPayload()); |
| | | case "payment_registration" -> processPaymentRegistration(request.getPayload()); |
| | | case "purchase_return_order" -> processPurchaseReturnOrder(request.getPayload()); |
| | | default -> AjaxResult.error("æä¸æ¯æè¯¥ä¸å¡ç±»å: " + businessType); |
| | | }; |
| | | } catch (Exception ex) { |
| | | return AjaxResult.error(toCustomerMessage(ex)); |
| | | } |
| | | } |
| | | |
| | | public List<AiChatSessionDto> listSessions(LoginUser loginUser) { |
| | | return aiChatSessionService.listCurrentUserSessions(loginUser); |
| | | } |
| | | |
| | | public List<AiChatMessageDto> listMessages(String memoryId, LoginUser loginUser) { |
| | | return aiChatSessionService.listCurrentUserMessages(memoryId, loginUser); |
| | | } |
| | | |
| | | public boolean deleteSession(String memoryId, LoginUser loginUser) { |
| | | aiSessionUserContext.remove(memoryId); |
| | | return aiChatSessionService.deleteCurrentUserSession(memoryId, loginUser); |
| | | } |
| | | |
| | | private String buildMultiFileContent(MultipartFile[] files) throws IOException { |
| | | StringBuilder builder = new StringBuilder(); |
| | | int totalLength = 0; |
| | | for (MultipartFile file : files) { |
| | | String text = aiFileTextExtractor.extractText(file); |
| | | if (!StringUtils.hasText(text)) { |
| | | continue; |
| | | } |
| | | String limitedText = text.length() > MAX_SINGLE_FILE_TEXT_LENGTH |
| | | ? text.substring(0, MAX_SINGLE_FILE_TEXT_LENGTH) |
| | | : text; |
| | | if (totalLength + limitedText.length() > MAX_TOTAL_FILE_TEXT_LENGTH) { |
| | | int remain = MAX_TOTAL_FILE_TEXT_LENGTH - totalLength; |
| | | if (remain <= 0) { |
| | | break; |
| | | } |
| | | limitedText = limitedText.substring(0, remain); |
| | | } |
| | | builder.append("\n--- æä»¶: ") |
| | | .append(file.getOriginalFilename()) |
| | | .append(" ---\n") |
| | | .append(limitedText) |
| | | .append('\n'); |
| | | totalLength += limitedText.length(); |
| | | } |
| | | return builder.toString(); |
| | | } |
| | | |
| | | private boolean containsImageFile(MultipartFile[] files) { |
| | | for (MultipartFile file : files) { |
| | | if (aiFileTextExtractor.isImageFile(file)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private List<String> resolveFileAccessPaths(List<StorageBlobVO> uploadedFiles) { |
| | | if (StringUtils.isEmpty(uploadedFiles)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | List<String> filePaths = new ArrayList<>(); |
| | | for (StorageBlobVO uploadedFile : uploadedFiles) { |
| | | if (uploadedFile == null) { |
| | | continue; |
| | | } |
| | | String selectedPath; |
| | | if (shouldUsePreviewPath(uploadedFile)) { |
| | | selectedPath = StringUtils.hasText(uploadedFile.getPreviewURL()) |
| | | ? uploadedFile.getPreviewURL() |
| | | : uploadedFile.getDownloadURL(); |
| | | } else { |
| | | selectedPath = StringUtils.hasText(uploadedFile.getDownloadURL()) |
| | | ? uploadedFile.getDownloadURL() |
| | | : uploadedFile.getPreviewURL(); |
| | | } |
| | | if (StringUtils.hasText(selectedPath)) { |
| | | filePaths.add(selectedPath); |
| | | } |
| | | } |
| | | return filePaths; |
| | | } |
| | | |
| | | private boolean shouldUsePreviewPath(StorageBlobVO uploadedFile) { |
| | | String contentType = uploadedFile.getContentType(); |
| | | if (StringUtils.hasText(contentType)) { |
| | | String normalized = contentType.toLowerCase(Locale.ROOT); |
| | | if (normalized.startsWith("image/") || "application/pdf".equals(normalized)) { |
| | | return true; |
| | | } |
| | | } |
| | | String filename = uploadedFile.getOriginalFilename(); |
| | | if (!StringUtils.hasText(filename) || !filename.contains(".")) { |
| | | return false; |
| | | } |
| | | String ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(Locale.ROOT); |
| | | return StringUtils.inStringIgnoreCase(ext, "png", "jpg", "jpeg", "webp", "bmp", "pdf"); |
| | | } |
| | | |
| | | private List<MultipartFile> copyFilesForUpload(MultipartFile[] files) throws IOException { |
| | | List<MultipartFile> copies = new ArrayList<>(); |
| | | for (MultipartFile file : files) { |
| | | copies.add(new InMemoryMultipartFile( |
| | | file.getName(), |
| | | file.getOriginalFilename(), |
| | | file.getContentType(), |
| | | file.getBytes() |
| | | )); |
| | | } |
| | | return copies; |
| | | } |
| | | |
| | | private static final class InMemoryMultipartFile implements MultipartFile { |
| | | private final String name; |
| | | private final String originalFilename; |
| | | private final String contentType; |
| | | private final byte[] bytes; |
| | | |
| | | private InMemoryMultipartFile(String name, String originalFilename, String contentType, byte[] bytes) { |
| | | this.name = name; |
| | | this.originalFilename = originalFilename; |
| | | this.contentType = contentType; |
| | | this.bytes = bytes == null ? new byte[0] : bytes; |
| | | } |
| | | |
| | | @Override |
| | | public String getName() { |
| | | return name; |
| | | } |
| | | |
| | | @Override |
| | | public String getOriginalFilename() { |
| | | return originalFilename; |
| | | } |
| | | |
| | | @Override |
| | | public String getContentType() { |
| | | return contentType; |
| | | } |
| | | |
| | | @Override |
| | | public boolean isEmpty() { |
| | | return bytes.length == 0; |
| | | } |
| | | |
| | | @Override |
| | | public long getSize() { |
| | | return bytes.length; |
| | | } |
| | | |
| | | @Override |
| | | public byte[] getBytes() { |
| | | return bytes.clone(); |
| | | } |
| | | |
| | | @Override |
| | | public InputStream getInputStream() { |
| | | return new ByteArrayInputStream(bytes); |
| | | } |
| | | |
| | | @Override |
| | | public void transferTo(File dest) throws IOException, IllegalStateException { |
| | | Files.write(dest.toPath(), bytes); |
| | | } |
| | | } |
| | | |
| | | private Flux<String> chatWithPurchaseVisionModel(String memoryId, |
| | | String userMessage, |
| | | String userPrompt, |
| | | MultipartFile[] files) { |
| | | return Flux.create(sink -> { |
| | | StringBuilder assistantReply = new StringBuilder(); |
| | | try { |
| | | List<Content> contents = new ArrayList<>(); |
| | | contents.add(TextContent.from(userPrompt)); |
| | | for (MultipartFile file : files) { |
| | | if (!aiFileTextExtractor.isImageFile(file)) { |
| | | continue; |
| | | } |
| | | contents.add(TextContent.from("ä¸é¢è¿å¼ å¾çæä»¶åï¼" + file.getOriginalFilename())); |
| | | contents.add(ImageContent.from(Image.builder() |
| | | .base64Data(Base64.getEncoder().encodeToString(file.getBytes())) |
| | | .mimeType(resolveImageMimeType(file)) |
| | | .build())); |
| | | } |
| | | |
| | | List<ChatMessage> messages = List.of( |
| | | SystemMessage.from("ä½ æ¯éè´ä¸å¡æä»¶åæå©æãè¯·ä»ææ¬åå¾çä¸è¯å«éè´å°è´¦ãéè´äº§åæç»ã仿¬¾æéè´§ä¿¡æ¯ï¼åªè¾åºåæ³ JSONã"), |
| | | UserMessage.from(contents) |
| | | ); |
| | | safeAppendMessages(memoryId, List.of(UserMessage.from("éè´å¤æä»¶åæ: " + userMessage))); |
| | | purchaseVisionStreamingChatModel.chat(messages, new StreamingChatResponseHandler() { |
| | | @Override |
| | | public void onPartialResponse(String partialResponse) { |
| | | if (partialResponse != null) { |
| | | assistantReply.append(partialResponse); |
| | | sink.next(partialResponse); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onCompleteResponse(ChatResponse completeResponse) { |
| | | if (StringUtils.hasText(assistantReply.toString())) { |
| | | safeAppendMessages(memoryId, List.of(AiMessage.from(assistantReply.toString()))); |
| | | } |
| | | sink.complete(); |
| | | } |
| | | |
| | | @Override |
| | | public void onError(Throwable error) { |
| | | sink.error(error); |
| | | } |
| | | }); |
| | | } catch (Exception ex) { |
| | | sink.next("å¾çæä»¶è¯»å失败ï¼è¯·ç¡®è®¤å¾çæ ¼å¼ä¸º pngãjpgãjpegãwebp æ bmpï¼ä¸å¤§å°ä¸è¶
è¿10MB"); |
| | | sink.complete(); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | private void safeAppendMessages(String memoryId, List<ChatMessage> messages) { |
| | | if (!StringUtils.hasText(memoryId) || StringUtils.isEmpty(messages)) { |
| | | return; |
| | | } |
| | | try { |
| | | mongoChatMemoryStore.appendMessages(memoryId, messages); |
| | | } catch (Exception ignored) { |
| | | } |
| | | } |
| | | |
| | | private String resolveImageMimeType(MultipartFile file) { |
| | | String contentType = file.getContentType(); |
| | | if (StringUtils.hasText(contentType) && contentType.startsWith("image/")) { |
| | | return contentType; |
| | | } |
| | | String filename = file.getOriginalFilename(); |
| | | String ext = ""; |
| | | if (StringUtils.hasText(filename) && filename.contains(".")) { |
| | | ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); |
| | | } |
| | | return switch (ext) { |
| | | case "jpg", "jpeg" -> "image/jpeg"; |
| | | case "webp" -> "image/webp"; |
| | | case "bmp" -> "image/bmp"; |
| | | default -> "image/png"; |
| | | }; |
| | | } |
| | | |
| | | private String buildPurchaseFileAnalyzePrompt(String message, String fileContent) { |
| | | return """ |
| | | ä½ æ¯éè´ä¸å¡æä»¶åæå©æãè¯·ä¸¥æ ¼æ ¹æ®ç¨æ·ä¸ä¼ çå¤ä¸ªæä»¶åç¨æ·è¦æ±æåéè´ä¸å¡æ°æ®ã |
| | | |
| | | ç¨æ·è¦æ±: |
| | | %s |
| | | |
| | | è¾åºè¦æ±: |
| | | 1. åªè¾åºåæ³ JSONï¼ä¸è¦ Markdownï¼ä¸è¦é¢å¤è§£éã |
| | | 2. JSON é¡¶å±å段åºå®ä¸º: |
| | | - success: boolean |
| | | - businessType: purchase_ledger | payment_registration | purchase_return_order | unknown |
| | | - action: confirm_required |
| | | - description: ä¸æè¯´æ |
| | | - confidence: 0å°1çå°æ° |
| | | - missingFields: ç¼ºå¤±åæ®µä¸æåç§°æ°ç»ï¼é¢å客æ·å±ç¤ºï¼ä¸è¦è¾åºè±æå段å |
| | | - warnings: é£é©æç¤ºæ°ç» |
| | | - payload: å¾
客æ·ç¡®è®¤çæ°æ®ï¼å段åå¿
须使ç¨å端 DTO åæ®µå |
| | | - preview: ç»å®¢æ·ç¡®è®¤ç¨ç䏿æè¦æ°ç» |
| | | 3. 妿å¯å¤æä¸ºéè´å°è´¦ï¼businessType ä½¿ç¨ purchase_ledgerï¼payload.purchaseLedgers 为éè´è®¢å/éè´å°è´¦æ°ç»: |
| | | - purchaseLedgers: éè´è®¢å/éè´å°è´¦æ°ç»ï¼æ¯æ¡è®°å½å段åå¿
é¡»ä¸ PurchaseLedgerDto ä¿æä¸è´ |
| | | - 产åæç»å¿
é¡»æ¾å¨æ¯æ¡éè´å°è´¦è®°å½ç productData åæ®µä¸ï¼productData ç±»å为 List<SalesLedgerProduct> |
| | | - ä¸è¦ä¼å
ä½¿ç¨ payload é¡¶å± productDataï¼é¡¶å± productData ä»
ä½ä¸ºæ§æ ¼å¼å
¼å®¹ |
| | | - æä»¶éçâéè´åå·âå°±æ¯âéè´ååå·âï¼ç»ä¸æ å°ä¸º purchaseContractNumber |
| | | - æä»¶éçâéå®åå·âå°±æ¯âéå®ååå·âï¼ç»ä¸æ å°ä¸º salesContractNo |
| | | - æææ¥æå段å¿
é¡»ä½¿ç¨ yyyy-MM-ddï¼ä¾å¦ 2026-04-30ï¼ä¸è¦è¾åº 4/30/26ã2026/4/30ã2026å¹´4æ30æ¥ æå¸¦æ¶åç§çæ ¼å¼ |
| | | - éè´å°è´¦ä¸éè¦å¨ payload ä¸ä¼ 审æ¹äººï¼ä¸è¦è¾åº approveUserIdsãapproverId |
| | | - missingFields åªå¡«åä¸å¡å¿
填使 æ³è¯å«çåæ®µï¼ä¸è¦æ PurchaseLedgerDto çææç©ºåæ®µé½å为缺失ï¼ç¼ºå¤±é¡¹å¿
é¡»å䏿ï¼ä¾å¦âä¾åºååç§°ââå«ç¨åä»·âï¼ä¸è¦å supplierIdãtaxInclusiveUnitPrice |
| | | - éè´å°è´¦ä¸»è¡¨å¿
å¡«åæ®µä»
æè¿äºå¤æ: purchaseContractNumberãsupplierName æ supplierId |
| | | - productData æ¯æ¡äº§åå¿
å¡«åæ®µ: productCategoryãspecificationModelãunitãquantityãtaxInclusiveUnitPrice æ taxInclusiveTotalPriceï¼å¦æåªæå«ç¨æ»ä»·åæ°éï¼å¿
é¡»è®¡ç® taxInclusiveUnitPriceï¼å¦æåªæå«ç¨åä»·åæ°éï¼å¿
é¡»è®¡ç® taxInclusiveTotalPrice |
| | | - 产ååæ®µæéè´å¯¼å
¥æ¥å£ PurchaseLedgerProductImportDto 对é½: éè´åå·ã产å大类ãè§æ ¼åå·ãåä½ãæ°éãç¨çãå«ç¨åä»·ãå«ç¨æ»ä»·ãå票类åãæ¯å¦è´¨æ£ |
| | | - éè´äº§å type åºå®ä¸º 2 |
| | | - purchaseLedgers æ¯æ¡è®°å½åªä½¿ç¨è¿äº PurchaseLedgerDto åæ®µå: |
| | | entryDateStart, entryDateEnd, id, purchaseContractNumber, supplierId, supplierName, isWhite, recorderId, recorderName, salesContractNo, salesContractNoId, projectName, entryDate, executionDate, remarks, attachmentMaterials, createdAt, updatedAt, salesLedgerId, hasChildren, Type, productData, tempFileIds, SalesLedgerFiles, phoneNumber, businessPersonId, productId, productModelId, invoiceNumber, invoiceAmount, ticketRegistrationId, contractAmount, receiptPaymentAmount, unReceiptPaymentAmount, type, paymentMethod, approvalStatus, templateName |
| | | - productData æ¯æ¡äº§ååªä½¿ç¨è¿äº SalesLedgerProduct åæ®µå: |
| | | productCategory, specificationModel, unit, quantity, taxRate, taxInclusiveUnitPrice, taxInclusiveTotalPrice, taxExclusiveTotalPrice, invoiceType, productId, productModelId, isChecked, type |
| | | 4. 妿å¯å¤æä¸ºä»æ¬¾ç»è®°ï¼businessType ä½¿ç¨ payment_registrationï¼payload.records ä¸ºä»æ¬¾ç»è®°æ°ç»ï¼å段尽éå
å« purchaseLedgerIdãsalesLedgerProductIdãcurrentPaymentAmountãpaymentMethodãpaymentDateã |
| | | 5. 妿å¯å¤æä¸ºéè´éè´§ï¼businessType ä½¿ç¨ purchase_return_orderï¼payload æ PurchaseReturnOrderDto ç»ç»ï¼æç»æ¾ purchaseReturnOrderProductsDtosã |
| | | 6. 缺å°ä¸å¡å¤çå¿
须忮µæ¶ï¼ä¸è¦ç¼é IDï¼æåæ®µæ¾å
¥ missingFieldsï¼å¹¶ä»è¿åå¯ç¡®è®¤çèç¨¿æ°æ®ã |
| | | 7. ææä¸æå
å®¹ç´æ¥ä¿çï¼ä¸è¦è½¬ä¹æ Unicodeã |
| | | |
| | | æä»¶å
容: |
| | | %s |
| | | """.formatted(message, fileContent); |
| | | } |
| | | |
| | | private AjaxResult processPurchaseLedger(Map<String, Object> payload) throws Exception { |
| | | if (payload.containsKey("purchaseLedgers")) { |
| | | return processPurchaseLedgerBatch(payload); |
| | | } |
| | | |
| | | Map<String, Object> normalizedPayload = normalizePurchaseLedgerMap(payload); |
| | | PurchaseLedgerDto dto = objectMapper.convertValue(normalizedPayload, PurchaseLedgerDto.class); |
| | | AjaxResult ledgerResult = validatePurchaseLedger(dto, 0); |
| | | if (ledgerResult != null) { |
| | | return ledgerResult; |
| | | } |
| | | AjaxResult supplierResult = fillSupplierIdByName(dto); |
| | | if (supplierResult != null) { |
| | | return supplierResult; |
| | | } |
| | | AjaxResult productResult = validatePurchaseProducts(dto.getProductData(), 0); |
| | | if (productResult != null) { |
| | | return productResult; |
| | | } |
| | | int result = purchaseLedgerService.addOrEditPurchase(dto); |
| | | return AjaxResult.success("éè´å°è´¦å·²å¤ç", result); |
| | | } |
| | | |
| | | private AjaxResult processPurchaseLedgerBatch(Map<String, Object> payload) throws Exception { |
| | | List<Map<String, Object>> purchaseLedgers = toMapList(payload.get("purchaseLedgers")); |
| | | if (purchaseLedgers.isEmpty()) { |
| | | return AjaxResult.error("purchaseLedgersä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | List<Map<String, Object>> topLevelProductData = toMapList(payload.get("productData")); |
| | | List<Map<String, Object>> results = new ArrayList<>(); |
| | | for (int i = 0; i < purchaseLedgers.size(); i++) { |
| | | Map<String, Object> ledgerMap = normalizePurchaseLedgerMap(purchaseLedgers.get(i)); |
| | | PurchaseLedgerDto dto = objectMapper.convertValue(ledgerMap, PurchaseLedgerDto.class); |
| | | AjaxResult ledgerResult = validatePurchaseLedger(dto, i); |
| | | if (ledgerResult != null) { |
| | | return ledgerResult; |
| | | } |
| | | AjaxResult supplierResult = fillSupplierIdByName(dto); |
| | | if (supplierResult != null) { |
| | | return supplierResult; |
| | | } |
| | | |
| | | List<SalesLedgerProduct> products = dto.getProductData(); |
| | | if (products == null || products.isEmpty()) { |
| | | products = matchProductsForLedger(ledgerMap, dto, topLevelProductData, purchaseLedgers.size() == 1); |
| | | dto.setProductData(products); |
| | | } |
| | | AjaxResult productResult = validatePurchaseProducts(products, i); |
| | | if (productResult != null) { |
| | | return productResult; |
| | | } |
| | | int result = purchaseLedgerService.addOrEditPurchase(dto); |
| | | |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("index", i); |
| | | item.put("purchaseContractNumber", dto.getPurchaseContractNumber()); |
| | | item.put("supplierId", dto.getSupplierId()); |
| | | item.put("supplierName", dto.getSupplierName()); |
| | | item.put("productCount", products.size()); |
| | | item.put("result", result); |
| | | results.add(item); |
| | | } |
| | | return AjaxResult.success("éè´å°è´¦å·²æ¹éå¤ç", results); |
| | | } |
| | | |
| | | private List<SalesLedgerProduct> matchProductsForLedger(Map<String, Object> ledgerMap, |
| | | PurchaseLedgerDto dto, |
| | | List<Map<String, Object>> productData, |
| | | boolean onlyOneLedger) { |
| | | List<SalesLedgerProduct> products = new ArrayList<>(); |
| | | for (Map<String, Object> productMap : productData) { |
| | | if (onlyOneLedger || productBelongsToLedger(productMap, ledgerMap, dto)) { |
| | | products.add(objectMapper.convertValue(normalizeSalesLedgerProductMap(productMap), SalesLedgerProduct.class)); |
| | | } |
| | | } |
| | | return products; |
| | | } |
| | | |
| | | private boolean productBelongsToLedger(Map<String, Object> productMap, Map<String, Object> ledgerMap, PurchaseLedgerDto dto) { |
| | | Long productPurchaseLedgerId = longValue(productMap, "purchaseLedgerId", "purchaseId", "éè´è®¢åid", "éè´å°è´¦id"); |
| | | if (productPurchaseLedgerId != null && dto.getId() != null && productPurchaseLedgerId.equals(dto.getId())) { |
| | | return true; |
| | | } |
| | | |
| | | Long productSalesLedgerId = longValue(productMap, "salesLedgerId"); |
| | | if (productSalesLedgerId != null && dto.getId() != null && productSalesLedgerId.equals(dto.getId())) { |
| | | return true; |
| | | } |
| | | |
| | | String productContractNo = stringValue(productMap, "purchaseContractNumber", "purchaseContractNo", "éè´ååå·", "éè´åå·", "éè´è®¢åå·"); |
| | | if (StringUtils.hasText(productContractNo) |
| | | && StringUtils.hasText(dto.getPurchaseContractNumber()) |
| | | && productContractNo.trim().equals(dto.getPurchaseContractNumber().trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String ledgerContractNo = stringValue(ledgerMap, "purchaseContractNumber", "purchaseContractNo", "éè´ååå·", "éè´åå·", "éè´è®¢åå·"); |
| | | if (StringUtils.hasText(productContractNo) |
| | | && StringUtils.hasText(ledgerContractNo) |
| | | && productContractNo.trim().equals(ledgerContractNo.trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String productSalesContractNo = stringValue(productMap, "salesContractNo", "salesContractNumber", "éå®ååå·", "éå®åå·", "éå®è®¢åå·"); |
| | | if (StringUtils.hasText(productSalesContractNo) |
| | | && StringUtils.hasText(dto.getSalesContractNo()) |
| | | && productSalesContractNo.trim().equals(dto.getSalesContractNo().trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String ledgerSalesContractNo = stringValue(ledgerMap, "salesContractNo", "salesContractNumber", "éå®ååå·", "éå®åå·", "éå®è®¢åå·"); |
| | | if (StringUtils.hasText(productSalesContractNo) |
| | | && StringUtils.hasText(ledgerSalesContractNo) |
| | | && productSalesContractNo.trim().equals(ledgerSalesContractNo.trim())) { |
| | | return true; |
| | | } |
| | | |
| | | String productSupplierName = stringValue(productMap, "supplierName", "ä¾åºååç§°"); |
| | | return StringUtils.hasText(productSupplierName) |
| | | && StringUtils.hasText(dto.getSupplierName()) |
| | | && productSupplierName.trim().equals(dto.getSupplierName().trim()); |
| | | } |
| | | |
| | | private Map<String, Object> normalizePurchaseLedgerMap(Map<String, Object> source) { |
| | | Map<String, Object> target = new LinkedHashMap<>(); |
| | | copyPurchaseLedgerDtoFields(source, target); |
| | | putDtoFieldIfPresent(source, target, "entryDateStart", "å½å
¥å¼å§æ¥æ", "å½å
¥æ¥æå¼å§"); |
| | | putDtoFieldIfPresent(source, target, "entryDateEnd", "å½å
¥ç»ææ¥æ", "å½å
¥æ¥æç»æ"); |
| | | putDtoFieldIfPresent(source, target, "id", "éè´å°è´¦id", "éè´è®¢åid", "主é®"); |
| | | putDtoFieldIfPresent(source, target, "purchaseContractNumber", "purchaseContractNo", "éè´ååå·", "éè´åå·", "éè´è®¢åå·"); |
| | | putDtoFieldIfPresent(source, target, "supplierId", "ä¾åºåid", "ä¾åºåID", "ä¾åºååç§°id", "ä¾åºååç§°ID"); |
| | | putDtoFieldIfPresent(source, target, "supplierName", "ä¾åºå", "ä¾åºååç§°"); |
| | | putDtoFieldIfPresent(source, target, "isWhite", "æ¯å¦ç½åå"); |
| | | putDtoFieldIfPresent(source, target, "recorderId", "å½å
¥äººid", "å½å
¥äººID", "å½å
¥äººå§åid", "å½å
¥äººå§åID"); |
| | | putDtoFieldIfPresent(source, target, "recorderName", "å½å
¥äºº", "å½å
¥äººå§å"); |
| | | putDtoFieldIfPresent(source, target, "salesContractNo", "salesContractNumber", "éå®ååå·", "éå®åå·", "éå®è®¢åå·"); |
| | | putDtoFieldIfPresent(source, target, "salesContractNoId", "éå®ååå·id", "éå®ååå·ID", "éå®åå·id", "éå®åå·ID"); |
| | | putDtoFieldIfPresent(source, target, "projectName", "项ç®", "项ç®åç§°"); |
| | | putDtoFieldIfPresent(source, target, "entryDate", "å½å
¥æ¥æ"); |
| | | putDtoFieldIfPresent(source, target, "executionDate", "ç¾è®¢æ¥æ", "ååç¾è®¢æ¥æ"); |
| | | putDtoFieldIfPresent(source, target, "remarks", "夿³¨", "说æ"); |
| | | putDtoFieldIfPresent(source, target, "attachmentMaterials", "éä»¶ææ", "éä»¶ææè·¯å¾æåç§°"); |
| | | putDtoFieldIfPresent(source, target, "createdAt", "å建æ¶é´", "è®°å½å建æ¶é´"); |
| | | putDtoFieldIfPresent(source, target, "updatedAt", "æ´æ°æ¶é´", "è®°å½æåæ´æ°æ¶é´"); |
| | | putDtoFieldIfPresent(source, target, "salesLedgerId", "éå®å°è´¦id", "éå®å°è´¦ID", "å
³èéå®å°è´¦ä¸»è¡¨ä¸»é®"); |
| | | putDtoFieldIfPresent(source, target, "hasChildren", "æ¯å¦æå级", "æ¯å¦ææç»"); |
| | | putDtoFieldIfPresent(source, target, "Type", "å°è´¦ç±»å", "ä¸å¡ç±»å"); |
| | | putDtoFieldIfPresent(source, target, "productData", "products", "产åæç»", "éè´äº§åæç»"); |
| | | putDtoFieldIfPresent(source, target, "tempFileIds", "ä¸´æ¶æä»¶id", "ä¸´æ¶æä»¶ID", "ä¸´æ¶æä»¶ids"); |
| | | putDtoFieldIfPresent(source, target, "SalesLedgerFiles", "éä»¶å表", "éå®å°è´¦éä»¶"); |
| | | putDtoFieldIfPresent(source, target, "phoneNumber", "ä¸å¡åææºå·", "ææºå·"); |
| | | putDtoFieldIfPresent(source, target, "businessPersonId", "ä¸å¡åid", "ä¸å¡åID"); |
| | | putDtoFieldIfPresent(source, target, "productId", "产åid", "产åID"); |
| | | putDtoFieldIfPresent(source, target, "productModelId", "产åè§æ ¼id", "产åè§æ ¼ID"); |
| | | putDtoFieldIfPresent(source, target, "invoiceNumber", "å票å·", "å票å·ç "); |
| | | putDtoFieldIfPresent(source, target, "invoiceAmount", "å票éé¢", "å票éé¢ï¼å
ï¼"); |
| | | putDtoFieldIfPresent(source, target, "ticketRegistrationId", "æ¥ç¥¨ç»è®°id", "æ¥ç¥¨ç»è®°ID"); |
| | | putDtoFieldIfPresent(source, target, "contractAmount", "ååéé¢", "ååéé¢ï¼äº§åå«ç¨æ»ä»·ï¼"); |
| | | putDtoFieldIfPresent(source, target, "receiptPaymentAmount", "æ¥ç¥¨éé¢", "å·²æ¥ç¥¨éé¢", "å·²æ¥ç¥¨éé¢(å
)"); |
| | | putDtoFieldIfPresent(source, target, "unReceiptPaymentAmount", "æªæ¥ç¥¨éé¢", "æªæ¥ç¥¨éé¢(å
)"); |
| | | putDtoFieldIfPresent(source, target, "type", "æä»¶ç±»å"); |
| | | putDtoFieldIfPresent(source, target, "paymentMethod", "仿¬¾æ¹å¼"); |
| | | putDtoFieldIfPresent(source, target, "approvalStatus", "审æ¹ç¶æ"); |
| | | putDtoFieldIfPresent(source, target, "templateName", "模æ¿åç§°"); |
| | | target.remove("approveUserIds"); |
| | | target.remove("approverId"); |
| | | normalizeNestedProductData(target); |
| | | attachImportStyleProductData(source, target); |
| | | if (target.get("type") == null) { |
| | | target.put("type", 2); |
| | | } |
| | | target.putIfAbsent("entryDate", LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)); |
| | | normalizePurchaseLedgerDateFields(target); |
| | | return target; |
| | | } |
| | | |
| | | private void attachImportStyleProductData(Map<String, Object> source, Map<String, Object> target) { |
| | | if (target.get("productData") != null) { |
| | | return; |
| | | } |
| | | Map<String, Object> productMap = normalizeSalesLedgerProductMap(source); |
| | | if (hasImportStyleProductData(productMap)) { |
| | | target.put("productData", List.of(productMap)); |
| | | } |
| | | } |
| | | |
| | | private boolean hasImportStyleProductData(Map<String, Object> productMap) { |
| | | return hasMapText(productMap, "productCategory") |
| | | || hasMapText(productMap, "specificationModel") |
| | | || productMap.get("quantity") != null |
| | | || productMap.get("taxInclusiveUnitPrice") != null |
| | | || productMap.get("taxInclusiveTotalPrice") != null; |
| | | } |
| | | |
| | | private boolean hasMapText(Map<String, Object> map, String key) { |
| | | Object value = map.get(key); |
| | | return value != null && StringUtils.hasText(String.valueOf(value)); |
| | | } |
| | | |
| | | private void normalizeNestedProductData(Map<String, Object> target) { |
| | | Object productDataValue = target.get("productData"); |
| | | if (productDataValue == null) { |
| | | return; |
| | | } |
| | | List<Map<String, Object>> productMaps = toMapList(productDataValue); |
| | | List<Map<String, Object>> normalizedProducts = new ArrayList<>(); |
| | | for (Map<String, Object> productMap : productMaps) { |
| | | normalizedProducts.add(normalizeSalesLedgerProductMap(productMap)); |
| | | } |
| | | target.put("productData", normalizedProducts); |
| | | } |
| | | |
| | | private Map<String, Object> normalizeSalesLedgerProductMap(Map<String, Object> source) { |
| | | Map<String, Object> target = new LinkedHashMap<>(); |
| | | copySalesLedgerProductFields(source, target); |
| | | putDtoFieldIfPresent(source, target, "productCategory", "产å大类", "产ååç§°", "产å", "åå", "ç©æåç§°"); |
| | | putDtoFieldIfPresent(source, target, "specificationModel", "è§æ ¼åå·", "åå·", "è§æ ¼", "产åè§æ ¼"); |
| | | putDtoFieldIfPresent(source, target, "unit", "åä½"); |
| | | putDtoFieldIfPresent(source, target, "quantity", "æ°é", "éè´æ°é"); |
| | | putDtoFieldIfPresent(source, target, "taxRate", "ç¨ç"); |
| | | putDtoFieldIfPresent(source, target, "taxInclusiveUnitPrice", "å«ç¨åä»·", "åä»·", "éè´åä»·", "å«ç¨ä»·æ ¼"); |
| | | putDtoFieldIfPresent(source, target, "taxInclusiveTotalPrice", "å«ç¨æ»ä»·", "æ»ä»·", "éè´éé¢", "éé¢", "ååéé¢"); |
| | | putDtoFieldIfPresent(source, target, "taxExclusiveTotalPrice", "ä¸å«ç¨æ»ä»·"); |
| | | putDtoFieldIfPresent(source, target, "invoiceType", "å票类å", "å票类å«"); |
| | | putDtoFieldIfPresent(source, target, "productId", "产åid", "产åID"); |
| | | putDtoFieldIfPresent(source, target, "productModelId", "产åè§æ ¼id", "产åè§æ ¼ID", "åå·id", "åå·ID"); |
| | | putDtoFieldIfPresent(source, target, "isChecked", "æ¯å¦è´¨æ£", "æ¯å¦è´¨æ£éª", "è´¨æ£"); |
| | | putDtoFieldIfPresent(source, target, "type", "å°è´¦ç±»å"); |
| | | normalizeProductAmounts(target); |
| | | target.putIfAbsent("type", 2); |
| | | return target; |
| | | } |
| | | |
| | | private void copySalesLedgerProductFields(Map<String, Object> source, Map<String, Object> target) { |
| | | String[] productFields = { |
| | | "id", "salesLedgerId", "warnNum", "productCategory", "specificationModel", "unit", |
| | | "speculativeTradingName", "quantity", "minStock", "taxRate", "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", "taxExclusiveTotalPrice", "invoiceType", "type", "ticketsNum", |
| | | "ticketsAmount", "futureTickets", "futureTicketsAmount", "invoiceNum", "noInvoiceNum", |
| | | "invoiceAmount", "noInvoiceAmount", "productId", "productModelId", "register", "registerDate", |
| | | "approveStatus", "pendingInvoiceTotal", "invoiceTotal", "pendingTicketsTotal", "ticketsTotal", |
| | | "isChecked", "isProduction" |
| | | }; |
| | | for (String field : productFields) { |
| | | if (source.containsKey(field)) { |
| | | target.put(field, source.get(field)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void normalizeProductAmounts(Map<String, Object> target) { |
| | | BigDecimal quantity = decimalValue(target.get("quantity")); |
| | | BigDecimal unitPrice = decimalValue(target.get("taxInclusiveUnitPrice")); |
| | | BigDecimal totalPrice = decimalValue(target.get("taxInclusiveTotalPrice")); |
| | | if (unitPrice == null && totalPrice != null && quantity != null && quantity.compareTo(BigDecimal.ZERO) != 0) { |
| | | target.put("taxInclusiveUnitPrice", totalPrice.divide(quantity, 6, RoundingMode.HALF_UP)); |
| | | } |
| | | if (totalPrice == null && unitPrice != null && quantity != null) { |
| | | target.put("taxInclusiveTotalPrice", unitPrice.multiply(quantity)); |
| | | } |
| | | BigDecimal taxRate = decimalValue(target.get("taxRate")); |
| | | totalPrice = decimalValue(target.get("taxInclusiveTotalPrice")); |
| | | if (target.get("taxExclusiveTotalPrice") == null && totalPrice != null && taxRate != null) { |
| | | BigDecimal divisor = BigDecimal.ONE.add(taxRate.divide(new BigDecimal("100"), 6, RoundingMode.HALF_UP)); |
| | | target.put("taxExclusiveTotalPrice", totalPrice.divide(divisor, 2, RoundingMode.HALF_UP)); |
| | | } |
| | | } |
| | | |
| | | private AjaxResult validatePurchaseProducts(List<SalesLedgerProduct> products, int ledgerIndex) { |
| | | if (products == null || products.isEmpty()) { |
| | | return null; |
| | | } |
| | | for (int i = 0; i < products.size(); i++) { |
| | | SalesLedgerProduct product = products.get(i); |
| | | String prefix = "第" + (ledgerIndex + 1) + "个éè´å°è´¦ç第" + (i + 1) + "æ¡äº§å"; |
| | | if (!StringUtils.hasText(product.getProductCategory())) { |
| | | return AjaxResult.error(prefix + "缺å°äº§ååç§°ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (!StringUtils.hasText(product.getSpecificationModel())) { |
| | | return AjaxResult.error(prefix + "缺å°è§æ ¼åå·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (!StringUtils.hasText(product.getUnit())) { |
| | | return AjaxResult.error(prefix + "缺å°åä½ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (product.getQuantity() == null) { |
| | | return AjaxResult.error(prefix + "ç¼ºå°æ°é"); |
| | | } |
| | | if (product.getTaxInclusiveUnitPrice() == null) { |
| | | return AjaxResult.error(prefix + "缺å°å«ç¨åä»·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (product.getTaxInclusiveTotalPrice() == null) { |
| | | return AjaxResult.error(prefix + "缺å°å«ç¨æ»ä»·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private AjaxResult validatePurchaseLedger(PurchaseLedgerDto dto, int ledgerIndex) { |
| | | String prefix = "第" + (ledgerIndex + 1) + "个éè´å°è´¦"; |
| | | if (!StringUtils.hasText(dto.getPurchaseContractNumber())) { |
| | | return AjaxResult.error(prefix + "缺å°éè´ååå·ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | if (dto.getSupplierId() == null && !StringUtils.hasText(dto.getSupplierName())) { |
| | | return AjaxResult.error(prefix + "缺å°ä¾åºååç§°ï¼è¯·è¡¥å
åå确认"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private void normalizePurchaseLedgerDateFields(Map<String, Object> target) { |
| | | normalizeDateField(target, "entryDate"); |
| | | normalizeDateField(target, "executionDate"); |
| | | normalizeDateField(target, "createdAt"); |
| | | normalizeDateField(target, "updatedAt"); |
| | | } |
| | | |
| | | private void normalizeDateField(Map<String, Object> target, String fieldName) { |
| | | Object value = target.get(fieldName); |
| | | if (value == null) { |
| | | return; |
| | | } |
| | | String normalizedDate = normalizeDateValue(value); |
| | | if (StringUtils.hasText(normalizedDate)) { |
| | | target.put(fieldName, normalizedDate); |
| | | } |
| | | } |
| | | |
| | | private String normalizeDateValue(Object value) { |
| | | if (value instanceof Date date) { |
| | | return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE); |
| | | } |
| | | if (value instanceof Number number) { |
| | | return LocalDate.of(1899, 12, 30) |
| | | .plusDays(number.longValue()) |
| | | .format(DateTimeFormatter.ISO_LOCAL_DATE); |
| | | } |
| | | |
| | | String text = String.valueOf(value).trim(); |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | if (text.length() >= 10 && text.charAt(4) == '-' && text.charAt(7) == '-') { |
| | | return text.substring(0, 10); |
| | | } |
| | | |
| | | String normalizedText = text.replace("å¹´", "-") |
| | | .replace("æ", "-") |
| | | .replace("æ¥", "") |
| | | .replace(".", "-") |
| | | .replace("/", "-") |
| | | .trim(); |
| | | DateTimeFormatter[] formatters = { |
| | | DateTimeFormatter.ofPattern("yyyy-M-d"), |
| | | DateTimeFormatter.ofPattern("M-d-yyyy"), |
| | | DateTimeFormatter.ofPattern("M-d-yy") |
| | | }; |
| | | for (DateTimeFormatter formatter : formatters) { |
| | | try { |
| | | return LocalDate.parse(normalizedText, formatter).format(DateTimeFormatter.ISO_LOCAL_DATE); |
| | | } catch (DateTimeParseException ignored) { |
| | | // Try the next supported input pattern. |
| | | } |
| | | } |
| | | return text; |
| | | } |
| | | |
| | | private void copyPurchaseLedgerDtoFields(Map<String, Object> source, Map<String, Object> target) { |
| | | String[] dtoFields = { |
| | | "entryDateStart", "entryDateEnd", "id", "purchaseContractNumber", |
| | | "supplierId", "supplierName", "isWhite", "recorderId", "recorderName", "salesContractNo", |
| | | "salesContractNoId", "projectName", "entryDate", "executionDate", "remarks", "attachmentMaterials", |
| | | "createdAt", "updatedAt", "salesLedgerId", "hasChildren", "Type", "productData", "tempFileIds", |
| | | "SalesLedgerFiles", "phoneNumber", "businessPersonId", "productId", "productModelId", "invoiceNumber", |
| | | "invoiceAmount", "ticketRegistrationId", "contractAmount", "receiptPaymentAmount", |
| | | "unReceiptPaymentAmount", "type", "paymentMethod", "approvalStatus", "templateName" |
| | | }; |
| | | for (String field : dtoFields) { |
| | | if (source.containsKey(field)) { |
| | | target.put(field, source.get(field)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void putDtoFieldIfPresent(Map<String, Object> source, Map<String, Object> target, String dtoField, String... aliases) { |
| | | if (target.containsKey(dtoField) && target.get(dtoField) != null) { |
| | | return; |
| | | } |
| | | for (String alias : aliases) { |
| | | Object value = source.get(alias); |
| | | if (value != null && StringUtils.hasText(String.valueOf(value))) { |
| | | target.put(dtoField, value); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | private List<Map<String, Object>> toMapList(Object value) { |
| | | if (value == null) { |
| | | return List.of(); |
| | | } |
| | | return objectMapper.convertValue(value, new TypeReference<List<Map<String, Object>>>() { |
| | | }); |
| | | } |
| | | |
| | | private String stringValue(Map<String, Object> map, String... keys) { |
| | | for (String key : keys) { |
| | | Object value = map.get(key); |
| | | if (value != null && StringUtils.hasText(String.valueOf(value))) { |
| | | return String.valueOf(value); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Long longValue(Map<String, Object> map, String... keys) { |
| | | String value = stringValue(map, keys); |
| | | if (!StringUtils.hasText(value)) { |
| | | return null; |
| | | } |
| | | try { |
| | | return Long.parseLong(value.trim()); |
| | | } catch (NumberFormatException ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private BigDecimal decimalValue(Object value) { |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | if (value instanceof BigDecimal decimal) { |
| | | return decimal; |
| | | } |
| | | if (value instanceof Number number) { |
| | | return new BigDecimal(String.valueOf(number)); |
| | | } |
| | | String text = String.valueOf(value) |
| | | .replace(",", "") |
| | | .replace("ï¼", "") |
| | | .replace("å
", "") |
| | | .replace("ï¿¥", "") |
| | | .trim(); |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | try { |
| | | return new BigDecimal(text); |
| | | } catch (NumberFormatException ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private String toCustomerMessage(Exception ex) { |
| | | String message = ex.getMessage(); |
| | | if (!StringUtils.hasText(message)) { |
| | | return "å¤ç失败ï¼è¯·æ£æ¥ç¡®è®¤æ°æ®åéè¯"; |
| | | } |
| | | if (message.contains("tax_inclusive_unit_price")) { |
| | | return "å¤ç失败ï¼äº§åæç»ç¼ºå°å«ç¨åä»·ï¼è¯·è¡¥å
åå确认"; |
| | | } |
| | | if (message.contains("tax_inclusive_total_price")) { |
| | | return "å¤ç失败ï¼äº§åæç»ç¼ºå°å«ç¨æ»ä»·ï¼è¯·è¡¥å
åå确认"; |
| | | } |
| | | if (message.contains("entryDate")) { |
| | | return "å¤ç失败ï¼å½å
¥æ¥ææ ¼å¼ä¸æ£ç¡®ï¼è¯·ä½¿ç¨ yyyy-MM-ddï¼ä¾å¦ 2026-04-30"; |
| | | } |
| | | if (message.contains("supplier")) { |
| | | return "å¤ç失败ï¼ä¾åºåä¿¡æ¯ä¸å®æ´ï¼è¯·ç¡®è®¤ä¾åºååç§°æä¾åºåID"; |
| | | } |
| | | if (message.contains("SQL") || message.contains("java.") || message.contains("Exception")) { |
| | | return "å¤ç失败ï¼ç¡®è®¤æ°æ®ä¸å®æ´ææ ¼å¼ä¸æ£ç¡®ï¼è¯·æ£æ¥å¿
å¡«åæ®µåéè¯"; |
| | | } |
| | | return "å¤ç失败ï¼" + message; |
| | | } |
| | | |
| | | private AjaxResult fillSupplierIdByName(PurchaseLedgerDto dto) { |
| | | if (dto.getSupplierId() != null) { |
| | | return null; |
| | | } |
| | | if (!StringUtils.hasText(dto.getSupplierName())) { |
| | | return AjaxResult.error("ä¾åºåIDä¸è½ä¸ºç©ºï¼æªè¯å«å°ä¾åºååç§°ï¼æ æ³èªå¨å¹é
ä¾åºåID"); |
| | | } |
| | | |
| | | SupplierManage supplier = supplierManageMapper.selectOne(new LambdaQueryWrapper<SupplierManage>() |
| | | .eq(SupplierManage::getSupplierName, dto.getSupplierName().trim()) |
| | | .last("limit 1")); |
| | | if (supplier == null) { |
| | | return AjaxResult.error("æªæ¾å°ä¾åºåï¼" + dto.getSupplierName() + "ï¼è¯·å
ç»´æ¤ä¾åºåææå¨éæ©ä¾åºåID"); |
| | | } |
| | | dto.setSupplierId(supplier.getId()); |
| | | return null; |
| | | } |
| | | |
| | | private AjaxResult processPaymentRegistration(Map<String, Object> payload) { |
| | | Object recordsValue = payload.get("records"); |
| | | List<PaymentRegistration> records; |
| | | if (recordsValue == null) { |
| | | records = Collections.singletonList(objectMapper.convertValue(payload, PaymentRegistration.class)); |
| | | } else { |
| | | records = objectMapper.convertValue(recordsValue, new TypeReference<List<PaymentRegistration>>() { |
| | | }); |
| | | } |
| | | int result = paymentRegistrationService.insertPaymentRegistration(records); |
| | | return AjaxResult.success("仿¬¾ç»è®°å·²å¤ç", result); |
| | | } |
| | | |
| | | private AjaxResult processPurchaseReturnOrder(Map<String, Object> payload) { |
| | | PurchaseReturnOrderDto dto = objectMapper.convertValue(payload, PurchaseReturnOrderDto.class); |
| | | Boolean result = purchaseReturnOrdersService.add(dto); |
| | | return AjaxResult.success("éè´éè´§åå·²å¤ç", result); |
| | | } |
| | | } |
| | |
| | | addQualityInspect(purchaseLedger, salesLedgerProduct); |
| | | } else { |
| | | //ç´æ¥å
¥åº |
| | | stockUtils.addStock(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId()); |
| | | stockUtils.addStockWithBatchNo(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId(),purchaseLedger.getPurchaseContractNumber()+"-"+salesLedgerProduct.getId()); |
| | | } |
| | | } |
| | | } else if (status.equals(3)) { |
| | |
| | | } |
| | | salesQuotationMapper.updateById(salesQuote); |
| | | } |
| | | // åºåºå®¡æ¹ä¿®æ¹ |
| | | // åºåºå®¡æ¹ä¿®æ¹=åè´§å®¡æ¹ |
| | | if (approveProcess.getApproveType().equals(7)) { |
| | | String[] split = approveProcess.getApproveReason().split(":"); |
| | | ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>() |
| | | .eq(ShippingInfo::getShippingNo, split[1]) |
| | | .eq(ShippingInfo::getShippingNo, approveProcess.getApproveReason()) |
| | | .orderByDesc(ShippingInfo::getCreateTime) |
| | | .last("limit 1")); |
| | | if (shippingInfo != null) { |
| | |
| | | } |
| | | shippingInfoMapper.updateById(shippingInfo); |
| | | } |
| | | //åºåæ£å |
| | | |
| | | } |
| | | fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVE_NODE, approveNode.getId(), approveNode.getStorageBlobDTOS()); |
| | |
| | | package com.ruoyi.approve.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | |
| | | import com.ruoyi.basic.enums.RecordTypeEnum; |
| | | import com.ruoyi.basic.utils.FileUtil; |
| | | import com.ruoyi.common.enums.FileNameType; |
| | | import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum; |
| | | import com.ruoyi.common.utils.OrderUtils; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.procurementrecord.utils.StockUtils; |
| | | import com.ruoyi.project.system.domain.SysDept; |
| | | import com.ruoyi.project.system.domain.SysNotice; |
| | | import com.ruoyi.project.system.domain.SysUser; |
| | |
| | | import com.ruoyi.purchase.mapper.PurchaseLedgerMapper; |
| | | import com.ruoyi.purchase.pojo.PurchaseLedger; |
| | | import com.ruoyi.sales.mapper.CommonFileMapper; |
| | | import com.ruoyi.sales.mapper.SalesLedgerProductMapper; |
| | | import com.ruoyi.sales.mapper.ShippingInfoMapper; |
| | | import com.ruoyi.sales.pojo.CommonFile; |
| | | import com.ruoyi.sales.pojo.SalesLedgerProduct; |
| | | import com.ruoyi.sales.pojo.ShippingInfo; |
| | | import com.ruoyi.sales.service.impl.CommonFileServiceImpl; |
| | | import lombok.RequiredArgsConstructor; |
| | |
| | | private final CommonFileServiceImpl commonFileService; |
| | | private final ISysNoticeService sysNoticeService; |
| | | private final PurchaseLedgerMapper purchaseLedgerMapper; |
| | | private final SalesLedgerProductMapper salesLedgerProductMapper; |
| | | private final StockUtils stockUtils; |
| | | private final ShippingInfoMapper shippingInfoMapper; |
| | | private final ApproveNodeMapper approveNodeMapper; |
| | | private final ApproveProcessConfigNodeService approveProcessConfigNodeService; |
| | |
| | | if (CollectionUtils.isEmpty(sysUsers)) throw new RuntimeException("å®¡æ ¸ç¨æ·ä¸åå¨"); |
| | | if (sysDept == null) throw new RuntimeException("é¨é¨ä¸åå¨"); |
| | | if (sysUser == null) throw new RuntimeException("ç³è¯·äººä¸åå¨"); |
| | | // String today = LocalDate.now().format(DATE_FORMAT); |
| | | // Long approveId = dailyRedisCounter.incrementAndGetByDb(); |
| | | // String formattedCount = String.format("%03d", approveId); |
| | | // //æµç¨ ID |
| | | // String approveID = today + formattedCount; |
| | | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); |
| | | ApproveProcess approveProcess = new ApproveProcess(); |
| | | String no = OrderUtils.countTodayByCreateTime(approveProcessMapper, "", "approve_id"); |
| | |
| | | || !StringUtils.hasText(approveProcessVO.getApproveReason())) { |
| | | throw new RuntimeException("å®¡æ ¸ç¨æ·ä¸åå¨"); |
| | | } |
| | | |
| | | purchaseLedgerMapper.update(null, new LambdaUpdateWrapper<PurchaseLedger>() |
| | | .eq(PurchaseLedger::getPurchaseContractNumber, approveProcessVO.getApproveReason()) |
| | | .set(PurchaseLedger::getApprovalStatus, 3)); |
| | | //éè´å
¥åº |
| | | PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>() |
| | | .eq(PurchaseLedger::getPurchaseContractNumber, approveProcessVO.getApproveReason()) |
| | | .last("limit 1")); |
| | | List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(new QueryWrapper<SalesLedgerProduct>() |
| | | .lambda().eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId()).eq(SalesLedgerProduct::getType, 2)); |
| | | for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) { |
| | | stockUtils.addStockWithBatchNo(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId(),purchaseLedger.getPurchaseContractNumber()+"-"+salesLedgerProduct.getId()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | @Data |
| | | public class ProductModelExportDto { |
| | | |
| | | @Excel(name = "产åç¼ç ") |
| | | private String productCode; |
| | | |
| | | @Excel(name = "è§æ ¼åå·") |
| | | private String model; |
| | | |
| | |
| | | ProductModel item = productModelList.get(i); |
| | | int rowNum = i + 2; |
| | | |
| | | if (StringUtils.isEmpty(item.getProductCode())) { |
| | | return AjaxResult.error("第 " + rowNum + " è¡å¯¼å
¥å¤±è´¥: [产åç¼ç ] ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (StringUtils.isEmpty(item.getModel())) { |
| | | return AjaxResult.error("第 " + rowNum + " è¡å¯¼å
¥å¤±è´¥: [è§æ ¼åå·] ä¸è½ä¸ºç©º"); |
| | | } |
| | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.github.xiaoymin.knife4j.core.util.CollectionUtils; |
| | | import com.ruoyi.basic.enums.ApplicationTypeEnum; |
| | | import com.ruoyi.basic.enums.RecordTypeEnum; |
| | | import com.ruoyi.basic.utils.FileUtil; |
| | | import com.ruoyi.common.utils.bean.BeanUtils; |
| | |
| | | }); |
| | | } |
| | | // å¤çå¾çä¸ä¼ |
| | | fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("file", RecordTypeEnum.DEVICE_REPAIR, id, deviceRepairDto.getStorageBlobDTOs()); |
| | | if (deviceRepairDto.getStorageBlobDTOs() != null) { |
| | | fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("file", RecordTypeEnum.DEVICE_REPAIR, id, deviceRepairDto.getStorageBlobDTOs()); |
| | | } |
| | | return AjaxResult.success(); |
| | | } |
| | | return AjaxResult.error(); |
| | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.SalesReturnDto; |
| | | import com.ruoyi.account.bean.vo.SalesReturnVo; |
| | | import com.ruoyi.procurementrecord.dto.ReturnManagementDto; |
| | | import com.ruoyi.procurementrecord.pojo.ReturnManagement; |
| | | import org.apache.ibatis.annotations.Param; |
| | |
| | | IPage<ReturnManagementDto> listPage(Page page, @Param("req") ReturnManagementDto returnManagement); |
| | | |
| | | ReturnManagementDto getReturnManagementDtoById(Long id); |
| | | |
| | | IPage<SalesReturnVo> listPageBySalesReturn(Page page, @Param("req") SalesReturnDto salesReturnDto); |
| | | } |
| | |
| | | import org.springframework.beans.BeanUtils; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.CollectionUtils; |
| | | import org.springframework.util.ObjectUtils; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.ArrayList; |
| | |
| | | |
| | | @Override |
| | | public boolean addReturnManagementDto(ReturnManagementDto returnManagementDto) { |
| | | String rt = OrderUtils.countTodayByCreateTime(returnManagementMapper, "RT","return_no"); |
| | | returnManagementDto.setReturnNo(rt); |
| | | if (ObjectUtils.isEmpty(returnManagementDto.getReturnNo())){ |
| | | String rt = OrderUtils.countTodayByCreateTime(returnManagementMapper, "RT","return_no"); |
| | | returnManagementDto.setReturnNo(rt); |
| | | } |
| | | save(returnManagementDto); |
| | | for (ReturnSaleProduct returnSaleProduct : returnManagementDto.getReturnSaleProducts()) { |
| | | returnSaleProduct.setReturnManagementId(returnManagementDto.getId()); |
| | |
| | | } |
| | | |
| | | /** |
| | | * åæ ¼å
¥åºå¸¦æ¹æ¬¡å· |
| | | * @param productModelId |
| | | * @param quantity |
| | | * @param recordType |
| | | * @param recordId |
| | | */ |
| | | public void addStockWithBatchNo(Long productModelId, BigDecimal quantity, String recordType, Long recordId, String batchNo) { |
| | | StockInventoryDto stockInventoryDto = new StockInventoryDto(); |
| | | stockInventoryDto.setRecordId(recordId); |
| | | stockInventoryDto.setRecordType(String.valueOf(recordType)); |
| | | stockInventoryDto.setQualitity(quantity); |
| | | stockInventoryDto.setProductModelId(productModelId); |
| | | stockInventoryDto.setBatchNo(batchNo); |
| | | stockInventoryService.addStockInRecordOnly(stockInventoryDto); |
| | | } |
| | | |
| | | /** |
| | | * åæ ¼åºåº |
| | | * |
| | | * @param productModelId |
| | |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "ProductionAccountDto", description = "production account query dto") |
| | | @Schema(name = "ProductionAccountDto", description = "çäº§æ ¸ç®æ¥è¯¢åæ°") |
| | | public class ProductionAccountDto extends ProductionAccount { |
| | | |
| | | @Schema(description = "sales contract no") |
| | | @Schema(description = "éå®ååå·") |
| | | private String salesContractNo; |
| | | |
| | | @Schema(description = "customer contract no") |
| | | @Schema(description = "客æ·ååå·") |
| | | private String customerContractNo; |
| | | |
| | | @Schema(description = "project name") |
| | | @Schema(description = "项ç®åç§°") |
| | | private String projectName; |
| | | |
| | | @Schema(description = "customer name") |
| | | @Schema(description = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "product category") |
| | | @Schema(description = "产åç±»å«") |
| | | private String productCategory; |
| | | |
| | | @Schema(description = "specification model") |
| | | @Schema(description = "è§æ ¼åå·") |
| | | private String specificationModel; |
| | | |
| | | @Schema(description = "scheduling user id") |
| | | @Schema(description = "æäº§äººåID") |
| | | private Long schedulingUserId; |
| | | |
| | | @Schema(description = "scheduling user name") |
| | | @Schema(description = "æäº§äººååç§°") |
| | | private String schedulingUserName; |
| | | |
| | | @Schema(description = "process") |
| | | @Schema(description = "å·¥åº") |
| | | private String process; |
| | | |
| | | @Schema(description = "date type(day/month)") |
| | | @Schema(description = "æ¥æç±»åï¼æå¤©/ææï¼") |
| | | private String dateType; |
| | | |
| | | @Schema(description = "day query date") |
| | | @Schema(description = "æå¤©æ¥è¯¢æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate entryDate; |
| | | |
| | | @Schema(description = "date range") |
| | | @Schema(description = "æ¥æèå´") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate[] dateRange; |
| | | |
| | | @Schema(description = "start date") |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate entryDateStart; |
| | | |
| | | @Schema(description = "end date") |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate entryDateEnd; |
| | |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate requiredDateEnd; |
| | | |
| | | @Schema(description = "éå®ååå·") |
| | | private String salesContractNo; |
| | | |
| | | } |
| | |
| | | |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @Data |
| | | @Schema(name = "ProductionProductMainDto", description = "production report query dto") |
| | | @Schema(name = "ProductionProductMainDto", description = "ç产æ¥å·¥æ¥è¯¢åæ°") |
| | | public class ProductionProductMainDto extends ProductionProductMain { |
| | | |
| | | @Schema(description = "product process route item id") |
| | | @Schema(description = "产åå·¥èºè·¯çº¿å·¥åºID") |
| | | private Long productProcessRouteItemId; |
| | | |
| | | @Schema(description = "production report id") |
| | | @Schema(description = "æ¥å·¥ID") |
| | | private Long productMainId; |
| | | |
| | | @Schema(description = "tenant id") |
| | | @Schema(description = "ç§æ·ID") |
| | | private Long tenantId; |
| | | |
| | | @Schema(description = "work order no") |
| | | @Schema(description = "å·¥åç¼å·") |
| | | private String workOrderNo; |
| | | |
| | | @Schema(description = "work order status") |
| | | @Schema(description = "å·¥åç¶æ") |
| | | private String workOrderStatus; |
| | | |
| | | @Schema(description = "nick name") |
| | | @Schema(description = "æµç§°") |
| | | private String nickName; |
| | | |
| | | @Schema(description = "quantity") |
| | | @Schema(description = "æ°é") |
| | | private BigDecimal quantity; |
| | | |
| | | @Schema(description = "scrap quantity") |
| | | @Schema(description = "æ¥åºæ°é") |
| | | private BigDecimal scrapQty; |
| | | |
| | | @Schema(description = "product name") |
| | | @Schema(description = "产ååç§°") |
| | | private String productName; |
| | | |
| | | @Schema(description = "product model name") |
| | | @Schema(description = "产åè§æ ¼åå·") |
| | | private String productModelName; |
| | | |
| | | @Schema(description = "unit") |
| | | @Schema(description = "åä½") |
| | | private String unit; |
| | | |
| | | @Schema(description = "sales contract no") |
| | | @Schema(description = "éå®ååå·") |
| | | private String salesContractNo; |
| | | |
| | | @Schema(description = "scheduling date") |
| | | @Schema(description = "æäº§æ¥æ") |
| | | private LocalDate schedulingDate; |
| | | |
| | | @Schema(description = "scheduling user name") |
| | | @Schema(description = "æäº§äººååç§°") |
| | | private String schedulingUserName; |
| | | |
| | | @Schema(description = "customer name") |
| | | @Schema(description = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "process") |
| | | @Schema(description = "å·¥åº") |
| | | private String process; |
| | | |
| | | @Schema(description = "salary quota") |
| | | @Schema(description = "å·¥èµå®é¢") |
| | | private BigDecimal workHours; |
| | | |
| | | @Schema(description = "wages") |
| | | @Schema(description = "å·¥èµ") |
| | | private BigDecimal wages; |
| | | |
| | | @Schema(description = "operation param list") |
| | | @Schema(description = "å·¥åºåæ°å表") |
| | | private List<ProductionOrderRoutingOperationParam> productionOperationParamList; |
| | | } |
| | |
| | | import java.time.LocalDate; |
| | | |
| | | @Data |
| | | @Schema(name = "ProductionAccountVo", description = "production account page result") |
| | | @Schema(name = "ProductionAccountVo", description = "çäº§æ ¸ç®åé¡µç»æ") |
| | | public class ProductionAccountVo { |
| | | |
| | | @Schema(description = "customer contract no") |
| | | @Schema(description = "客æ·ååå·") |
| | | private String customerContractNo; |
| | | |
| | | @Schema(description = "project name") |
| | | @Schema(description = "项ç®åç§°") |
| | | private String projectName; |
| | | |
| | | @Schema(description = "customer name") |
| | | @Schema(description = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "product category") |
| | | @Schema(description = "产åç±»å«") |
| | | private String productCategory; |
| | | |
| | | @Schema(description = "specification model") |
| | | @Schema(description = "è§æ ¼åå·") |
| | | private String specificationModel; |
| | | |
| | | @Schema(description = "unit") |
| | | @Schema(description = "åä½") |
| | | private String unit; |
| | | |
| | | @Schema(description = "scheduling user id") |
| | | @Schema(description = "æäº§äººåID") |
| | | private Long schedulingUserId; |
| | | |
| | | @Schema(description = "scheduling user name") |
| | | @Schema(description = "æäº§äººååç§°") |
| | | private String schedulingUserName; |
| | | |
| | | @Schema(description = "wages") |
| | | @Schema(description = "å·¥èµ") |
| | | private BigDecimal wages; |
| | | |
| | | @Schema(description = "finished quantity") |
| | | @Schema(description = "宿æ°é") |
| | | private BigDecimal finishedNum; |
| | | |
| | | @Schema(description = "salary quota") |
| | | @Schema(description = "å·¥èµå®é¢") |
| | | private BigDecimal workHours; |
| | | |
| | | @Schema(description = "output rate") |
| | | @Schema(description = "å·¥æ¶") |
| | | private BigDecimal workHour; |
| | | |
| | | @Schema(description = "产åºç") |
| | | private String outputRate; |
| | | |
| | | @Schema(description = "process") |
| | | @Schema(description = "å·¥åº") |
| | | private String process; |
| | | |
| | | @Schema(description = "scheduling date") |
| | | @Schema(description = "æäº§æ¥æ") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private LocalDate schedulingDate; |
| | | |
| | | @Schema(description = "scheduling month(yyyy-MM)") |
| | | @Schema(description = "æäº§æä»½(yyyy-MM)") |
| | | private String schedulingMonth; |
| | | } |
| | |
| | | |
| | | @Schema(description = "æ¯å¦ç»æï¼") |
| | | private Boolean endOrder; |
| | | |
| | | @Schema(description = "ç±»å åºå计æ¶å计件(0计æ¶1计件)") |
| | | private Integer type; |
| | | } |
| | |
| | | package com.ruoyi.production.bean.vo; |
| | | |
| | | import com.ruoyi.basic.dto.StorageBlobVO; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import com.ruoyi.production.pojo.ProductionOrder; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | |
| | | private String customerName; |
| | | |
| | | @Schema(description = "产ååç§°") |
| | | @Excel(name = "产ååç§°",sort = 2) |
| | | private String productName; |
| | | |
| | | @Schema(description = "è§æ ¼åå·") |
| | | @Excel(name = "è§æ ¼",sort = 3) |
| | | private String model; |
| | | |
| | | @Schema(description = "å·¥èºè·¯çº¿ç¼ç ") |
| | | @Excel(name = "å·¥èºè·¯çº¿ç¼å·",sort = 4) |
| | | private String processRouteCode; |
| | | |
| | | @Schema(description = "产åå¾ç") |
| | |
| | | private String bomNo; |
| | | |
| | | @Schema(description = "宿è¿åº¦") |
| | | @Excel(name = "宿è¿åº¦",sort = 7) |
| | | private BigDecimal completionStatus; |
| | | |
| | | @Schema(description = "æ¯å¦å·²éæ") |
| | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | |
| | | @Data |
| | |
| | | @Schema(description = "æ¥å·¥ä¸»ä¿¡æ¯") |
| | | private ProductionProductMain reportMain; |
| | | |
| | | @Schema(description = "å·¥æ¶") |
| | | private BigDecimal workHour; |
| | | |
| | | @Schema(description = "æ¥å·¥äº§åºæç»") |
| | | private List<ProductionProductOutput> reportOutputList; |
| | | |
| | |
| | | @Schema(description = "æ¥å·¥ä¸»ä¿¡æ¯") |
| | | private ProductionProductMain reportMain; |
| | | |
| | | @Schema(description = "å·¥æ¶") |
| | | private BigDecimal workHour; |
| | | |
| | | @Schema(description = "è´¨æ£ä¸»ä¿¡æ¯") |
| | | private QualityInspect inspect; |
| | | |
| | |
| | | package com.ruoyi.production.controller; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Log; |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import com.ruoyi.production.bean.dto.ProductionOrderDto; |
| | | import com.ruoyi.production.bean.vo.ProductionOrderPickVo; |
| | |
| | | import com.ruoyi.production.bean.vo.ProductionOrderWorkOrderDetailVo; |
| | | import com.ruoyi.production.pojo.ProductionOrder; |
| | | import com.ruoyi.production.service.ProductionOrderService; |
| | | import com.ruoyi.sales.dto.SalesLedgerDto; |
| | | import com.ruoyi.sales.vo.SalesLedgerVo; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.media.Content; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | @RestController |
| | |
| | | public R updateOrder(@RequestBody ProductionOrderDto productionOrderDto) { |
| | | return R.ok(productionOrderService.updateOrder(productionOrderDto)); |
| | | } |
| | | |
| | | |
| | | @Log(title = "ç产订å导åº", businessType = BusinessType.EXPORT) |
| | | @PostMapping("/export") |
| | | public void export(HttpServletResponse response, ProductionOrderDto dto) { |
| | | IPage<ProductionOrderVo> productionOrderVoIPage = productionOrderService.pageProductionOrder(new Page<>(-1, -1), dto); |
| | | List<ProductionOrderVo> records = productionOrderVoIPage.getRecords(); |
| | | ExcelUtil<ProductionOrderVo> util = new ExcelUtil<>(ProductionOrderVo.class); |
| | | util.exportExcel(response, records, "çäº§è®¢åæ°æ®"); |
| | | } |
| | | } |
| | |
| | | |
| | | import com.baomidou.mybatisplus.annotation.*; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.ruoyi.framework.aspectj.lang.annotation.Excel; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | |
| | | private Long productModelId; |
| | | |
| | | @Schema(description = "ç产订åå·") |
| | | @Excel(name = "ç产订å",sort = 0) |
| | | private String npsNo; |
| | | |
| | | @Schema(description = "å½å
¥æ¶é´") |
| | |
| | | private Long technologyRoutingId; |
| | | |
| | | @Schema(description = "éæ±æ°éãæå¨æ°å¢æ¶å¿
å¡«ä¸å¿
é¡»å¤§äº 0ï¼å¦æä¼ äº productionPlanIdsï¼åå¯ç±ç³»ç»èªå¨å¸¦åºã") |
| | | @Excel(name = "éæ±æ°é",sort = 5) |
| | | private BigDecimal quantity; |
| | | |
| | | @Schema(description = "宿æ°é") |
| | | @Excel(name = "宿æ°é",sort = 6) |
| | | private BigDecimal completeQuantity; |
| | | |
| | | @Schema(description = "å¼å§æ¥æ") |
| | | @Excel(name = "å¼å§æ¥æ",sort = 8,dateFormat = "yyyy-MM-dd") |
| | | private LocalDateTime startTime; |
| | | |
| | | @Schema(description = "ç»ææ¥æ") |
| | | @Excel(name = "ç»ææ¥æ",sort = 9,dateFormat = "yyyy-MM-dd") |
| | | private LocalDateTime endTime; |
| | | |
| | | @Schema(description = "å建人ID") |
| | |
| | | @Schema(description = "计å宿æ¶é´") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd") |
| | | @Excel(name = "计å宿æ¶é´",sort = 10,dateFormat = "yyyy-MM-dd") |
| | | private LocalDate planCompleteTime; |
| | | |
| | | @Schema(description = "ç¶æï¼1.å¾
å¼å§ 2.è¿è¡ä¸ 3.已宿 4.已忶 5.å·²ç»æï¼") |
| | | @Excel(name = "ç¶æ",sort = 1,readConverterExp = "1=å¾
å¼å§,2=è¿è¡ä¸,3=已宿,4=已忶,5=å·²ç»æ") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "æ¯å¦ç»æï¼") |
| | |
| | | |
| | | @Schema(description = "å·¥åºè¡¨id") |
| | | private Long technologyOperationId; |
| | | |
| | | @Schema(description = "ç±»å åºå计æ¶å计件ï¼0计æ¶ï¼1计件") |
| | | private Integer type; |
| | | } |
| | |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | |
| | | @Schema(description = "å·¥æ¶") |
| | | private BigDecimal workHour; |
| | | |
| | | } |
| | |
| | | BigDecimal totalReturnQty = oldReturnQty.add(currentReturnQty); |
| | | if (currentReturnQty.compareTo(BigDecimal.ZERO) > 0) { |
| | | String returnBatchNo = resolveInventoryBatchNoFromStored(oldPick.getBatchNo()); |
| | | addInventory(oldPick.getId(), oldPick.getProductModelId(), returnBatchNo, currentReturnQty, FEED_RETURN_IN_RECORD_TYPE); |
| | | addInventoryRecordOnly(oldPick.getId(), oldPick.getProductModelId(), returnBatchNo, currentReturnQty, FEED_RETURN_IN_RECORD_TYPE); |
| | | } |
| | | |
| | | BigDecimal actualQty = defaultDecimal(oldPick.getQuantity()) |
| | |
| | | } |
| | | } |
| | | |
| | | private void addInventoryRecordOnly(Long recordId, |
| | | Long productModelId, |
| | | String batchNo, |
| | | BigDecimal quantity, |
| | | String stockInRecordType) { |
| | | // ä»
è®°å½å
¥åºç³è¯·ï¼ä¸åå®¡æ ¸éè¿ã |
| | | BigDecimal addQuantity = defaultDecimal(quantity); |
| | | if (addQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return; |
| | | } |
| | | try { |
| | | StockInventoryDto stockInventoryDto = new StockInventoryDto(); |
| | | stockInventoryDto.setProductModelId(productModelId); |
| | | stockInventoryDto.setBatchNo(batchNo); |
| | | stockInventoryDto.setQualitity(addQuantity); |
| | | stockInventoryDto.setRecordType(stockInRecordType); |
| | | stockInventoryDto.setRecordId(recordId == null ? 0L : recordId); |
| | | stockInventoryService.addStockInRecordOnly(stockInventoryDto); |
| | | } catch (ServiceException ex) { |
| | | throw ex; |
| | | } catch (Exception ex) { |
| | | throw new ServiceException("éæå
¥åºè®°å½ä¿å失败ï¼" + ex.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private List<ProductionOrderPickDto> resolvePickItems(ProductionOrderPickDto dto) { |
| | | // è§£ææ°å¢åºæ¯ç颿æç»éåã |
| | | if (dto == null) { |
| | |
| | | ProductionOrder update = new ProductionOrder(); |
| | | update.setId(productionOrder.getId()); |
| | | update.setTechnologyRoutingId(targetRoutingId); |
| | | // æä¹
åæè¾åºå¤çç»æ |
| | | if (!this.updateById(update)) { |
| | | throw new ServiceException("ç»å®å·¥èºè·¯çº¿å¤±è´¥"); |
| | | } |
| | |
| | | clearProductionSnapshot(productionOrderId); |
| | | ProductionOrderBom orderBom = syncProductionOrderBomSnapshot(productionOrder, technologyRouting); |
| | | |
| | | //ç产订åå·¥èºè·¯çº¿è¡¨ |
| | | ProductionOrderRouting orderRouting = new ProductionOrderRouting(); |
| | | orderRouting.setProductionOrderId(productionOrder.getId()); |
| | | orderRouting.setTechnologyRoutingId(technologyRouting.getId()); |
| | |
| | | orderRouting.setDescription(technologyRouting.getDescription()); |
| | | orderRouting.setBomId(technologyRouting.getBomId()); |
| | | orderRouting.setOrderBomId(orderBom == null ? null : orderBom.getId()); |
| | | // æä¹
åæè¾åºå¤çç»æ |
| | | productionOrderRoutingMapper.insert(orderRouting); |
| | | |
| | | int syncedParamCount = 0; |
| | |
| | | targetOperation.setIsQuality(sourceOperation.getIsQuality()); |
| | | targetOperation.setOperationName(operationNameMap.get(sourceOperation.getTechnologyOperationId())); |
| | | targetOperation.setTechnologyOperationId(sourceOperation.getTechnologyOperationId()); |
| | | targetOperation.setType(sourceOperation.getType()); |
| | | productionOrderRoutingOperationMapper.insert(targetOperation); |
| | | |
| | | boolean isLastOperation = lastDragSort != null && Objects.equals(sourceOperation.getDragSort(), lastDragSort); |
| | |
| | | : workOrderPage.getRecords().stream() |
| | | .filter(Objects::nonNull) |
| | | .sorted(Comparator.comparing(ProductionOperationTaskVo::getId, Comparator.nullsLast(Comparator.naturalOrder()))) |
| | | .collect(Collectors.toList()); |
| | | if (workOrderList == null || workOrderList.isEmpty()) { |
| | | .toList(); |
| | | if (workOrderList.isEmpty()) { |
| | | detailVo.setWorkOrderList(Collections.emptyList()); |
| | | return detailVo; |
| | | } |
| | |
| | | |
| | | ProductionOrderWorkOrderDetailVo.ReportDetail reportDetail = new ProductionOrderWorkOrderDetailVo.ReportDetail(); |
| | | reportDetail.setReportMain(reportMain); |
| | | reportDetail.setWorkHour(reportMain.getWorkHour()); |
| | | reportDetail.setReportOutputList(reportOutputMap.getOrDefault(reportMainId, Collections.emptyList())); |
| | | reportDetail.setReportParamList(reportParamMap.getOrDefault(reportMainId, Collections.emptyList())); |
| | | reportDetailList.add(reportDetail); |
| | |
| | | inspectDetail.setReportId(reportMainId); |
| | | inspectDetail.setReportNo(reportMain.getProductNo()); |
| | | inspectDetail.setReportMain(reportMain); |
| | | inspectDetail.setWorkHour(reportMain.getWorkHour()); |
| | | inspectDetail.setInspect(inspect); |
| | | inspectDetail.setInspectParamList(inspectParamMap.getOrDefault(inspect.getId(), Collections.emptyList())); |
| | | inspectDetail.setInspectFileList(inspectFileMap.getOrDefault(inspect.getId(), Collections.emptyList())); |
| | |
| | | productionProductMain.setUserName(user == null ? dto.getUserName() : user.getNickName()); |
| | | productionProductMain.setProductionOperationTaskId(taskId); |
| | | productionProductMain.setStatus(0); |
| | | productionProductMain.setWorkHour(dto.getWorkHour()); |
| | | productionProductMainMapper.insert(productionProductMain); |
| | | syncOperationParamInputValue(dto, routingOperation.getId(), productionProductMain.getId()); |
| | | |
| ÎļþÃû´Ó src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderVo.java ÐÞ¸Ä |
| | |
| | | package com.ruoyi.purchase.vo; |
| | | package com.ruoyi.purchase.dto; |
| | | |
| | | import com.ruoyi.purchase.pojo.PurchaseReturnOrders; |
| | | import lombok.AllArgsConstructor; |
| | |
| | | @Data |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | public class PurchaseReturnOrderVo extends PurchaseReturnOrders { |
| | | public class PurchaseReturnOrderHasAllInfoDto extends PurchaseReturnOrders { |
| | | //ä¾åºååç§° |
| | | private String supplierName; |
| | | |
| | |
| | | import com.ruoyi.purchase.dto.PurchaseReturnOrderDto; |
| | | import com.ruoyi.purchase.pojo.PurchaseReturnOrders; |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.purchase.vo.PurchaseReturnOrderVo; |
| | | import com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | |
| | | */ |
| | | @Mapper |
| | | public interface PurchaseReturnOrdersMapper extends BaseMapper<PurchaseReturnOrders> { |
| | | IPage<PurchaseReturnOrderVo> listPage(Page page, @Param("params") PurchaseReturnOrderDto purchaseReturnOrder); |
| | | IPage<PurchaseReturnOrderHasAllInfoDto> listPage(Page page, @Param("params") PurchaseReturnOrderDto purchaseReturnOrder); |
| | | |
| | | PurchaseReturnOrderHasAllInfoDto getPurchaseReturnOrderHasAllInfoById(@Param("id") @NotNull Long id); |
| | | } |
| | |
| | | import com.ruoyi.purchase.pojo.PurchaseReturnOrders; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ruoyi.purchase.vo.PurchaseReturnDetailsVo; |
| | | import com.ruoyi.purchase.vo.PurchaseReturnOrderVo; |
| | | import com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto; |
| | | |
| | | import jakarta.validation.constraints.NotNull; |
| | | |
| | |
| | | * @since 2026-03-06 11:44:38 |
| | | */ |
| | | public interface PurchaseReturnOrdersService extends IService<PurchaseReturnOrders> { |
| | | IPage<PurchaseReturnOrderVo> listPage(Page page, PurchaseReturnOrderDto purchaseReturnOrderDto); |
| | | IPage<PurchaseReturnOrderHasAllInfoDto> listPage(Page page, PurchaseReturnOrderDto purchaseReturnOrderDto); |
| | | |
| | | Boolean add(PurchaseReturnOrderDto purchaseReturnOrderDto); |
| | | |
| | |
| | | } |
| | | purchaseLedgerMapper.updateById(purchaseLedger); |
| | | } |
| | | // 6.éè´å®¡æ ¸æ°å¢ï¼å®¡æ¹ç®¡çæªé
ç½®éè´å®¡æ¹äººæ¶ï¼å®¡æ¹æå¡ä¼èªå¨ç½®ä¸ºå®¡æ¹éè¿ã |
| | | addApproveByPurchase(loginUser, purchaseLedger); |
| | | |
| | | // 4. å¤çåè¡¨æ°æ® |
| | | List<SalesLedgerProduct> productList = purchaseLedgerDto.getProductData(); |
| | | if (productList != null && !productList.isEmpty()) { |
| | | handleSalesLedgerProducts(purchaseLedger.getId(), productList, purchaseLedgerDto.getType()); |
| | | } |
| | | //æ°å¢åæææ£éª 审æ¹ä¹åæçææ£éª |
| | | // if (productList != null) { |
| | | // for (SalesLedgerProduct saleProduct : productList) { |
| | | // //æ¯å¦æ¨éè´¨æ£ï¼å¦ætrue就添å |
| | | // if (saleProduct.getIsChecked()) { |
| | | // addQualityInspect(purchaseLedger, saleProduct); |
| | | // } |
| | | // } |
| | | // } |
| | | // 6.éè´å®¡æ ¸æ°å¢ï¼å®¡æ¹ç®¡çæªé
ç½®éè´å®¡æ¹äººæ¶ï¼å®¡æ¹æå¡ä¼èªå¨ç½®ä¸ºå®¡æ¹éè¿ã |
| | | addApproveByPurchase(loginUser, purchaseLedger); |
| | | // 5. è¿ç§»ä¸´æ¶æä»¶å°æ£å¼ç®å½ |
| | | fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.PURCHASE_LEDGER, purchaseLedger.getId(), purchaseLedgerDto.getStorageBlobDTOS()); |
| | | return 1; |
| | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.account.pojo.AccountIncome; |
| | | import com.ruoyi.account.service.AccountIncomeService; |
| | | import com.ruoyi.common.enums.SaleEnum; |
| | | import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum; |
| | | import com.ruoyi.common.utils.DateUtils; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.framework.security.LoginUser; |
| | | import com.ruoyi.procurementrecord.utils.StockUtils; |
| | | import com.ruoyi.purchase.dto.PurchaseReturnOrderDto; |
| | | import com.ruoyi.purchase.dto.PurchaseReturnOrderProductsDto; |
| | | import com.ruoyi.purchase.mapper.PurchaseLedgerMapper; |
| | | import com.ruoyi.purchase.mapper.PurchaseReturnOrderProductsMapper; |
| | | import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper; |
| | | import com.ruoyi.purchase.pojo.PurchaseLedger; |
| | | import com.ruoyi.purchase.pojo.PurchaseReturnOrderProducts; |
| | | import com.ruoyi.purchase.pojo.PurchaseReturnOrders; |
| | | import com.ruoyi.purchase.service.PurchaseReturnOrdersService; |
| | | import com.ruoyi.purchase.vo.PurchaseReturnDetailsVo; |
| | | import com.ruoyi.purchase.vo.PurchaseReturnOrderVo; |
| | | import com.ruoyi.sales.mapper.SalesLedgerProductMapper; |
| | | import com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto; |
| | | import com.ruoyi.sales.pojo.SalesLedgerProduct; |
| | | import com.ruoyi.sales.service.ISalesLedgerService; |
| | | import com.ruoyi.stock.mapper.StockOutRecordMapper; |
| | | import com.ruoyi.stock.pojo.StockOutRecord; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | |
| | | private final PurchaseReturnOrderProductsMapper purchaseReturnOrderProductsMapper; |
| | | private final ISalesLedgerService salesLedgerService; |
| | | private final AccountIncomeService accountIncomeService; |
| | | private final StockUtils stockUtils; |
| | | private final SalesLedgerProductMapper salesLedgerProductMapper; |
| | | private final PurchaseLedgerMapper purchaseLedgerMapper; |
| | | private final StockOutRecordMapper stockOutRecordMapper; |
| | | |
| | | @Override |
| | | public IPage<PurchaseReturnOrderVo> listPage(Page page, PurchaseReturnOrderDto purchaseReturnOrderDto) { |
| | | public IPage<PurchaseReturnOrderHasAllInfoDto> listPage(Page page, PurchaseReturnOrderDto purchaseReturnOrderDto) { |
| | | return purchaseReturnOrdersMapper.listPage(page, purchaseReturnOrderDto); |
| | | } |
| | | |
| | |
| | | // è¿é为æ°å¢å æ¤id为null |
| | | purchaseReturnOrderProductsDto.setId(null); |
| | | purchaseReturnOrderProductsMapper.insert(purchaseReturnOrderProductsDto); |
| | | //åºåéè¦åºåº(éè´éè´§) |
| | | PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(purchaseReturnOrderDto.getPurchaseLedgerId()); |
| | | SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(purchaseReturnOrderProductsDto.getSalesLedgerProductId()); |
| | | stockUtils.substractStock(salesLedgerProduct.getProductModelId(), purchaseReturnOrderProductsDto.getReturnQuantity(), StockOutQualifiedRecordTypeEnum.PURCHASE_RETURN_STOCK_OUT.getCode(), purchaseReturnOrderDto.getId(), purchaseLedger.getPurchaseContractNumber()+"-"+salesLedgerProduct.getId()); |
| | | } |
| | | }else { |
| | | throw new RuntimeException("è¯·éæ©éè´§åå"); |
| | |
| | | |
| | | @Override |
| | | public PurchaseReturnDetailsVo getPurchaseReturnOrderDtoById(Long id) { |
| | | PurchaseReturnOrders purchaseReturnOrders = purchaseReturnOrdersMapper.selectById(id); |
| | | PurchaseReturnOrderHasAllInfoDto purchaseReturnOrders = purchaseReturnOrdersMapper.getPurchaseReturnOrderHasAllInfoById(id); |
| | | PurchaseReturnDetailsVo purchaseReturnOrderDto = BeanUtil.copyProperties(purchaseReturnOrders, PurchaseReturnDetailsVo.class); |
| | | // æ¥è¯¢åºä»å
·ä½å¯¹åºçéè´§ |
| | | LambdaQueryWrapper<PurchaseReturnOrderProducts> queryWrapper = new LambdaQueryWrapper<>(); |
| | |
| | | LambdaUpdateWrapper<PurchaseReturnOrderProducts> updateWrapper = new LambdaUpdateWrapper<>(); |
| | | updateWrapper.eq(PurchaseReturnOrderProducts::getPurchaseReturnOrderId, id); |
| | | purchaseReturnOrderProductsMapper.delete(updateWrapper); |
| | | |
| | | //(éè´éè´§çæ°æ®éè¦å æ) |
| | | stockOutRecordMapper.delete(Wrappers.<StockOutRecord>lambdaQuery() |
| | | .eq(StockOutRecord::getRecordType,StockOutQualifiedRecordTypeEnum.PURCHASE_RETURN_STOCK_OUT.getCode()) |
| | | .eq(StockOutRecord::getRecordId, id)); |
| | | // è´¢å¡ |
| | | LambdaUpdateWrapper<AccountIncome> updateWrapperAccountIncome = new LambdaUpdateWrapper<>(); |
| | | updateWrapperAccountIncome.eq(AccountIncome::getBusinessId, id); |
| | |
| | | package com.ruoyi.purchase.vo; |
| | | |
| | | import com.ruoyi.purchase.pojo.PurchaseReturnOrders; |
| | | import com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto; |
| | | import com.ruoyi.sales.pojo.SalesLedgerProduct; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | |
| | | @Data |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | public class PurchaseReturnDetailsVo extends PurchaseReturnOrders implements Serializable { |
| | | public class PurchaseReturnDetailsVo extends PurchaseReturnOrderHasAllInfoDto implements Serializable { |
| | | |
| | | private List<PurchaseReturnOrderProductsDetailVo> purchaseReturnOrderProductsDetailVoList; |
| | | |
| | |
| | | if (CollUtil.isEmpty(list)) { |
| | | return AjaxResult.success(list); |
| | | } |
| | | // |
| | | List<Long> productIds = list.stream().map(SalesLedgerProduct::getId).collect(Collectors.toList()); |
| | | List<SimpleReturnOrderGroupDto> groupListByProductIds = purchaseReturnOrderProductsMapper.getReturnOrderGroupListByProductIds(productIds); |
| | | Map<Long, BigDecimal> returnOrderGroupDtoMap = groupListByProductIds.stream().collect(Collectors.toMap(SimpleReturnOrderGroupDto::getSalesLedgerProductId, item -> item.getSumReturnQuantity())); |
| | |
| | | if (item.getFutureTicketsAmount().compareTo(BigDecimal.ZERO) == 0) { |
| | | item.setFutureTicketsAmount(BigDecimal.ZERO); |
| | | } |
| | | // ProcurementPageDto procurementDto = new ProcurementPageDto(); |
| | | // procurementDto.setSalesLedgerProductId(item.getId()); |
| | | // procurementDto.setProductCategory(item.getProductCategory()); |
| | | // IPage<ProcurementPageDtoCopy> result = procurementRecordService.listPageCopyByProduction(new Page<>(1,-1), procurementDto); |
| | | // BigDecimal stockQuantity = stockUtils.getStockQuantity(item.getProductModelId()).get("stockQuantity"); |
| | | |
| | | // ProcurementPageDtoCopy procurementDtoCopy = result.getRecords().get(0); |
| | | if (item.getApproveStatus() != 2) { |
| | | if (item.getHasSufficientStock() == 0) { |
| | | item.setApproveStatus(0); |
| | |
| | | ApproveProcessVO approveProcessVO = new ApproveProcessVO(); |
| | | approveProcessVO.setApproveType(7); |
| | | approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId()); |
| | | approveProcessVO.setApproveReason(req.getType() + ":" +sh); |
| | | approveProcessVO.setApproveReason(sh);//åè´§ç¼å· |
| | | approveProcessVO.setApproveUserIds(req.getApproveUserIds()); |
| | | approveProcessVO.setApproveUser(loginUser.getUserId()); |
| | | approveProcessVO.setApproveTime(LocalDate.now().toString()); |
| | |
| | | } |
| | | |
| | | @GetMapping("/getDateil/{id}") |
| | | @Operation(summary = "éè¿idæ¥è¯¢è¯¦æ
") |
| | | public R getDateil(@PathVariable("id") Long id) { |
| | | return R.ok(shippingInfoService.getDetail(id)); |
| | | } |
| | | |
| | | @GetMapping("/getDateilByShippingNo") |
| | | @Operation(summary = "éè¿åè´§åå·æ¥è¯¢è¯¦æ
") |
| | | public R getDateilByShippingNo(String shippingNo) { |
| | | return R.ok(shippingInfoService.getDateilByShippingNo(shippingNo)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.sales.dto; |
| | | |
| | | import com.ruoyi.sales.pojo.ShippingInfo; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | //åè´§å®¡æ¹æ¥ç详æ
|
| | | @Data |
| | | public class ShippingApproveDto { |
| | | |
| | | private ShippingInfo shippingInfo; |
| | | |
| | | private List<ShippingProductDetailDto> shippingProductDetailDtoList; |
| | | } |
| | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.ruoyi.account.bean.dto.SalesOutboundDto; |
| | | import com.ruoyi.account.bean.vo.SalesOutboundVo; |
| | | import com.ruoyi.sales.dto.SalesLedgerProductDto; |
| | | import com.ruoyi.sales.dto.ShippingInfoDto; |
| | | import com.ruoyi.sales.pojo.ShippingInfo; |
| | | import com.ruoyi.sales.pojo.ShippingProductDetail; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | import java.util.List; |
| | |
| | | |
| | | List<ShippingInfo> getShippingInfoByCustomerName(String customerName); |
| | | |
| | | List<ShippingProductDetail> getDateil(Long id); |
| | | IPage<SalesOutboundVo> listPageByOutbound(Page page, @Param("req") SalesOutboundDto salesOutboundDto); |
| | | } |
| | |
| | | import com.ruoyi.sales.dto.ShippingProductDetailDto; |
| | | import com.ruoyi.sales.pojo.ShippingProductDetail; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | import java.util.List; |
| | | |
| | |
| | | public interface ShippingProductDetailMapper extends BaseMapper<ShippingProductDetail> { |
| | | |
| | | List<ShippingProductDetailDto> getDetail(Long id); |
| | | |
| | | List<ShippingProductDetailDto> getDateilByShippingNo(@Param("shippingNo") String shippingNo); |
| | | } |
| | |
| | | private Boolean isProduction; |
| | | |
| | | @TableField(exist = false) |
| | | @Schema(description = "å¾
åè´§æ°é") |
| | | private BigDecimal noQuantity; |
| | | } |
| | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ruoyi.sales.dto.SalesLedgerProductDto; |
| | | import com.ruoyi.sales.dto.ShippingApproveDto; |
| | | import com.ruoyi.sales.dto.ShippingInfoDto; |
| | | import com.ruoyi.sales.dto.ShippingProductDetailDto; |
| | | import com.ruoyi.sales.pojo.ShippingInfo; |
| | |
| | | boolean add(ShippingInfoDto req); |
| | | |
| | | List<ShippingProductDetailDto> getDetail(Long id); |
| | | |
| | | ShippingApproveDto getDateilByShippingNo(String shippingNo); |
| | | } |
| | |
| | | |
| | | @Override |
| | | public List<SalesLedgerProduct> selectSalesLedgerProductList(SalesLedgerProduct salesLedgerProduct) { |
| | | // LambdaQueryWrapper<SalesLedgerProduct> queryWrapper = new LambdaQueryWrapper<>(); |
| | | // queryWrapper.eq(SalesLedgerProduct::getSalesLedgerId, salesLedgerProduct.getSalesLedgerId()) |
| | | // .eq(SalesLedgerProduct::getType, salesLedgerProduct.getType()); |
| | | List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectSalesLedgerProductList(salesLedgerProduct); |
| | | if(!CollectionUtils.isEmpty(salesLedgerProducts)){ |
| | | salesLedgerProducts.forEach(item -> { |
| | |
| | | public boolean add(SalesQuotationDto salesQuotationDto) { |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | SalesQuotation salesQuotation = new SalesQuotation(); |
| | | BeanUtils.copyProperties(salesQuotationDto, salesQuotation); |
| | | salesQuotation.setId(null); |
| | | Customer customer = customerMapper.selectById(Long.valueOf(salesQuotationDto.getCustomerId())); |
| | | if (ObjectUtils.isNotEmpty(customer)) { |
| | | salesQuotation.setCustomer(customer.getCustomerName()); |
| | |
| | | import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum; |
| | | import com.ruoyi.procurementrecord.utils.StockUtils; |
| | | import com.ruoyi.sales.dto.SalesLedgerProductDto; |
| | | import com.ruoyi.sales.dto.ShippingApproveDto; |
| | | import com.ruoyi.sales.dto.ShippingInfoDto; |
| | | import com.ruoyi.sales.dto.ShippingProductDetailDto; |
| | | import com.ruoyi.sales.mapper.SalesLedgerProductMapper; |
| | |
| | | } |
| | | //æ£ååºå |
| | | if(!"å·²åè´§".equals(byId.getStatus())){ |
| | | // SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(byId.getSalesLedgerProductId()); |
| | | List<ShippingProductDetail> shippingProductDetails = shippingProductDetailMapper.selectList(new LambdaQueryWrapper<ShippingProductDetail>().eq(ShippingProductDetail::getShippingInfoId, req.getId())); |
| | | if (CollectionUtils.isEmpty(shippingProductDetails)) { |
| | | throw new RuntimeException("åè´§ä¿¡æ¯ä¸åå¨"); |
| | |
| | | public List<ShippingProductDetailDto> getDetail(Long id) { |
| | | return shippingProductDetailMapper.getDetail(id); |
| | | } |
| | | |
| | | @Override |
| | | public ShippingApproveDto getDateilByShippingNo(String shippingNo) { |
| | | ShippingApproveDto shippingApproveDto = new ShippingApproveDto(); |
| | | ShippingInfo shippingInfo = new ShippingInfo(); |
| | | shippingInfo.setShippingNo(shippingNo); |
| | | shippingApproveDto.setShippingInfo(shippingInfoMapper.listPage(new Page(1, -1),shippingInfo).getRecords().get(0)); |
| | | List<ShippingProductDetailDto> dateilByShippingNo = shippingProductDetailMapper.getDateilByShippingNo(shippingNo); |
| | | shippingApproveDto.setShippingProductDetailDtoList(dateilByShippingNo); |
| | | return shippingApproveDto; |
| | | } |
| | | } |
| | |
| | | import com.ruoyi.framework.aspectj.lang.enums.BusinessType; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import com.ruoyi.staff.dto.StaffOnJobDto; |
| | | import com.ruoyi.staff.dto.StaffOnJobExcelDto; |
| | | import com.ruoyi.staff.pojo.StaffContract; |
| | | import com.ruoyi.staff.pojo.StaffOnJob; |
| | | import com.ruoyi.staff.service.IStaffOnJobService; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import org.springframework.util.CollectionUtils; |
| | | import org.springframework.web.bind.annotation.*; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | import com.ruoyi.staff.dto.StaffOnJobExcelDto; |
| | | |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import jakarta.validation.Valid; |
| | | import java.util.List; |
| | | |
| | | /** |
| | |
| | | * @return |
| | | */ |
| | | @PostMapping("/renewContract/{id}") |
| | | public AjaxResult renewContract(@PathVariable("id") Long id, @RequestBody StaffContract staffContract) { |
| | | public AjaxResult renewContract(@PathVariable Long id, @RequestBody StaffContract staffContract) { |
| | | return AjaxResult.success(staffOnJobService.renewContract(id, staffContract)); |
| | | } |
| | | |
| | |
| | | @Schema(description = "æ¯å¦è´¨æ£") |
| | | private Boolean isQuality; |
| | | |
| | | @Schema(description = "ç±»å åºå计æ¶å计件") |
| | | @Schema(description = "ç±»å åºå计æ¶å计件ï¼0计æ¶ï¼1计件") |
| | | private Integer type; |
| | | |
| | | @Schema(description = "设å¤id") |
| | |
| | | @Schema(description = "é¨é¨ID") |
| | | @TableField(fill = FieldFill.INSERT) |
| | | private Long deptId; |
| | | |
| | | @Schema(description = "ç±»å åºå计æ¶å计件ï¼0计æ¶ï¼1计件") |
| | | private Integer type; |
| | | } |
| | |
| | | routingOperation.setProductModelId(resolveOutputProductModelId(bomStructure, structureById, technologyRouting.getProductModelId())); |
| | | routingOperation.setTechnologyOperationId(bomStructure.getOperationId()); |
| | | routingOperation.setDragSort(dragSort++); |
| | | routingOperation.setIsQuality(getOperationQuality(bomStructure.getOperationId())); |
| | | TechnologyOperation technologyOperation = getOperation(bomStructure.getOperationId()); |
| | | routingOperation.setIsQuality(technologyOperation != null ? technologyOperation.getIsQuality() : null); |
| | | routingOperation.setIsProduction(technologyOperation != null ? technologyOperation.getIsProduction() : null); |
| | | routingOperation.setType(technologyOperation != null ? technologyOperation.getType() : null); |
| | | technologyRoutingOperationMapper.insert(routingOperation); |
| | | syncRoutingOperationParams(routingOperation.getId(), bomStructure.getOperationId()); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è´¨æ£æ è¯ä»¥å·¥åºåºç¡è¡¨å®ä¹ä¸ºåã |
| | | */ |
| | | private Boolean getOperationQuality(Long operationId) { |
| | | TechnologyOperation technologyOperation = technologyOperationMapper.selectById(operationId); |
| | | return technologyOperation != null ? technologyOperation.getIsQuality() : null; |
| | | private TechnologyOperation getOperation(Long operationId) { |
| | | if (operationId == null) { |
| | | return null; |
| | | } |
| | | return technologyOperationMapper.selectById(operationId); |
| | | } |
| | | |
| | | private String buildProcessRouteCode(Long id) { |
| | |
| | | left join sales_ledger sl on si.sales_ledger_id = sl.id |
| | | where rm.id = #{id} |
| | | </select> |
| | | </mapper> |
| | | <select id="listPageBySalesReturn" resultType="com.ruoyi.account.bean.vo.SalesReturnVo"> |
| | | select rm.id, |
| | | rm.return_no, |
| | | c.customer_name, |
| | | si.shipping_no, |
| | | rm.make_time, |
| | | rm.refund_amount, |
| | | rm.return_reason, |
| | | rm.make_time, |
| | | sl.sales_contract_no |
| | | from return_management rm |
| | | left join shipping_info si on rm.shipping_id = si.id |
| | | left join customer c on rm.customer_id = c.id |
| | | left join sales_ledger sl on si.sales_ledger_id = sl.id |
| | | where rm.status=1 |
| | | <if test="req.returnNo != null and req.returnNo != ''"> |
| | | and rm.return_no like concat('%',#{req.returnNo},'%') |
| | | </if> |
| | | <if test="req.customerName != null and req.customerName != ''"> |
| | | and c.customer_name like concat('%',#{req.customerName},'%') |
| | | </if> |
| | | <if test="req.startDate != null and req.endDate != null"> |
| | | AND DATE_FORMAT(rm.make_time, '%Y-%m-%d') BETWEEN #{startDate} AND #{endDate} |
| | | </if> |
| | | order by rm.id DESC |
| | | </select> |
| | | </mapper> |
| | |
| | | pa.scheduling_user_id as schedulingUserId, |
| | | pa.scheduling_user_name as schedulingUserName, |
| | | cast(sum( |
| | | ifnull(pa.work_hours, 0) * ifnull(pa.finished_num, 0) * |
| | | case |
| | | when substring_index(pm.model, '*', -1) regexp '^[0-9]+(\\.[0-9]+)?$' |
| | | then cast(substring_index(pm.model, '*', -1) as decimal(18,4)) |
| | | else 1 |
| | | when poro.type = 0 then ifnull(pa.work_hours, 0) * ifnull(ppm.work_hour, 0) |
| | | else ifnull(pa.work_hours, 0) * ifnull(pa.finished_num, 0) * |
| | | case |
| | | when substring_index(pm.model, '*', -1) regexp '^[0-9]+(\\.[0-9]+)?$' |
| | | then cast(substring_index(pm.model, '*', -1) as decimal(18,4)) |
| | | else 1 |
| | | end |
| | | end |
| | | ) as decimal(18,4)) as wages, |
| | | cast(sum(ifnull(pa.finished_num, 0)) as decimal(18,4)) as finishedNum, |
| | | cast(sum(ifnull(pa.work_hours, 0)) as decimal(18,4)) as workHours, |
| | | cast(sum(ifnull(ppm.work_hour, 0)) as decimal(18,4)) as workHour, |
| | | case |
| | | when sum(ifnull(ppo.quantity, 0) + ifnull(ppo.scrapQty, 0)) = 0 then '0%' |
| | | else concat( |
| | |
| | | pm.model as model, |
| | | pm.unit as unit, |
| | | poro.operation_name as operationName, |
| | | poro.type as type, |
| | | IFNULL(scrapStat.scrapQty, 0) AS scrapQty, |
| | | ROUND(IFNULL(pot.complete_quantity, 0) / NULLIF(pot.plan_quantity, 0) * 100, 2) AS completionStatus, |
| | | CASE |
| | |
| | | <result column="update_time" property="updateTime" /> |
| | | <result column="drag_sort" property="dragSort" /> |
| | | <result column="is_quality" property="isQuality" /> |
| | | <result column="type" property="type" /> |
| | | <result column="create_user" property="createUser" /> |
| | | <result column="dept_id" property="deptId" /> |
| | | </resultMap> |
| | |
| | | <if test="c.requiredDateStart != null and c.requiredDateEnd != null"> |
| | | and pp.required_date between #{c.requiredDateStart} and #{c.requiredDateEnd} |
| | | </if> |
| | | <if test="c.salesContractNo != null and c.salesContractNo != ''"> |
| | | and sl.sales_contract_no like concat('%', #{c.salesContractNo}, '%') |
| | | </if> |
| | | </if> |
| | | </where> |
| | | ORDER BY COALESCE(pp.id) DESC |
| | |
| | | ifnull(ppo.scrap_qty, 0) as scrapQty, |
| | | date(pa.scheduling_date) as schedulingDate, |
| | | pa.scheduling_user_name as schedulingUserName, |
| | | cast(ifnull(ppm.work_hour, 0) as decimal(18,4)) as workHour, |
| | | cast(ifnull(pa.work_hours, 0) as decimal(18,4)) as workHours, |
| | | cast( |
| | | ifnull(pa.work_hours, 0) * ifnull(pa.finished_num, 0) * |
| | | case |
| | | when substring_index(pm.model, '*', -1) regexp '^[0-9]+(\\.[0-9]+)?$' |
| | | then cast(substring_index(pm.model, '*', -1) as decimal(18,4)) |
| | | else 1 |
| | | when poro.type = 0 then ifnull(pa.work_hours, 0) * ifnull(ppm.work_hour, 0) |
| | | else ifnull(pa.work_hours, 0) * ifnull(pa.finished_num, 0) * |
| | | case |
| | | when substring_index(pm.model, '*', -1) regexp '^[0-9]+(\\.[0-9]+)?$' |
| | | then cast(substring_index(pm.model, '*', -1) as decimal(18,4)) |
| | | else 1 |
| | | end |
| | | end |
| | | as decimal(18,4) |
| | | ) as wages |
| | |
| | | <result column="create_time" property="createTime" /> |
| | | <result column="update_time" property="updateTime" /> |
| | | </resultMap> |
| | | |
| | | <select id="listPage" resultType="com.ruoyi.purchase.vo.PurchaseReturnOrderVo"> |
| | | <sql id="getPurchaseReturnOrderHasAllInfoFormAndColumn"> |
| | | SELECT |
| | | pro.*, |
| | | sm.supplier_name as supplierName, |
| | | pl.purchase_contract_number as purchaseContractNumber |
| | | pro.*, |
| | | sm.supplier_name as supplier_name, |
| | | pl.purchase_contract_number as purchase_contract_number |
| | | FROM purchase_return_orders pro |
| | | LEFT JOIN supplier_manage sm ON pro.supplier_id = sm.id |
| | | LEFT JOIN purchase_ledger pl ON pl.id = pro.purchase_ledger_id |
| | | where 1=1 |
| | | LEFT JOIN supplier_manage sm ON pro.supplier_id = sm.id |
| | | LEFT JOIN purchase_ledger pl ON pl.id = pro.purchase_ledger_id |
| | | </sql> |
| | | <select id="listPage" resultType="com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto"> |
| | | <include refid="getPurchaseReturnOrderHasAllInfoFormAndColumn"/> |
| | | <where> |
| | | <if test="params.no != null and params.no != '' "> |
| | | AND pro.no LIKE CONCAT('%',#{params.no},'%') |
| | | </if> |
| | |
| | | <if test="params.createUser != null"> |
| | | AND pro.create_user = #{params.createUser} |
| | | </if> |
| | | </where> |
| | | ORDER BY pro.create_time DESC |
| | | </select> |
| | | <select id="getPurchaseReturnOrderHasAllInfoById" |
| | | resultType="com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto"> |
| | | <include refid="getPurchaseReturnOrderHasAllInfoFormAndColumn"/> |
| | | where pro.id = #{id} |
| | | </select> |
| | | </mapper> |
| | |
| | | AND T3.invoice_date = #{invoiceRegistrationProductDto.invoiceDate} |
| | | </if> |
| | | </where> |
| | | ORDER BY T1.create_time DESC |
| | | ORDER BY T1.create_time DESC, T1.id DESC |
| | | </select> |
| | | |
| | | <select id="invoiceRegistrationProductPage" resultType="com.ruoyi.sales.dto.InvoiceRegistrationProductDto"> |
| | |
| | | %H:%i:%s')+interval 1 day |
| | | </if> |
| | | </where> |
| | | ORDER BY T1.create_time DESC |
| | | ORDER BY T1.create_time DESC, T1.id DESC |
| | | </select> |
| | | </mapper> |
| | |
| | | left join sales_ledger sl on si.sales_ledger_id = sl.id |
| | | where si.status = 'å·²åè´§' and sl.customer_name = #{customerName} |
| | | </select> |
| | | <select id="listPageByOutbound" resultType="com.ruoyi.account.bean.vo.SalesOutboundVo"> |
| | | SELECT |
| | | sor.id, |
| | | sor.outbound_batches, |
| | | sl.customer_name, |
| | | s.shipping_date, |
| | | p.product_name, |
| | | slp.specification_model, |
| | | slp.stock_out_num, |
| | | s.shipping_no, |
| | | sl.sales_contract_no |
| | | FROM shipping_info s |
| | | LEFT JOIN sales_ledger sl ON s.sales_ledger_id = sl.id |
| | | LEFT JOIN sales_ledger_product slp ON s.sales_ledger_product_id = slp.id and slp.type = 1 |
| | | left join product_model pm on slp.product_model_id = pm.id |
| | | left join product p on pm.product_id = p.id |
| | | left join stock_out_record sor on sor.record_id = s.id and sor.record_type='13' |
| | | WHERE s.status='å·²åè´§' |
| | | <if test="req.outboundBatches != null and req.outboundBatches != ''"> |
| | | AND sor.outbound_batches LIKE CONCAT('%',#{req.outboundBatches},'%') |
| | | </if> |
| | | <if test="req.customerName != null and req.customerName != ''"> |
| | | AND sl.customer_name LIKE CONCAT('%',#{req.customerName},'%') |
| | | </if> |
| | | <if test="req.startDate != null and req.endDate != null"> |
| | | AND s.shipping_date BETWEEN #{startDate} AND #{endDate} |
| | | </if> |
| | | order by sor.id DESC |
| | | </select> |
| | | </mapper> |
| | |
| | | left join product p on p.id = pm.product_id |
| | | where spd.shipping_info_id = #{id} |
| | | </select> |
| | | <select id="getDateilByShippingNo" resultType="com.ruoyi.sales.dto.ShippingProductDetailDto"> |
| | | select si.batch_no, pm.model as specification_model, p.product_name, spd.quantity as delivery_quantity |
| | | from shipping_product_detail spd |
| | | left join shipping_info sp on sp.id = spd.shipping_info_id |
| | | left join stock_inventory si on si.id = spd.stock_inventory_id |
| | | left join product_model pm on pm.id = si.product_model_id |
| | | left join product p on p.id = pm.product_id |
| | | where sp.shipping_no = #{shippingNo} |
| | | </select> |
| | | |
| | | </mapper> |
| | |
| | | staff_on_job.*, |
| | | sp.post_name as postName, |
| | | sd.dept_name as deptName, |
| | | t1.contract_start_time |
| | | MIN(t1.contract_start_time) as contract_start_time, -- åææ©ååå¼å§æ¶é´ |
| | | MAX(t1.contract_end_time) as contract_end_time |
| | | FROM staff_on_job |
| | | LEFT JOIN |
| | | sys_post sp ON sp.post_id = staff_on_job.sys_post_id |
| | | LEFT JOIN |
| | | sys_dept sd ON sd.dept_id = staff_on_job.sys_dept_id |
| | | LEFT JOIN sys_post sp ON sp.post_id = staff_on_job.sys_post_id |
| | | LEFT JOIN sys_dept sd ON sd.dept_id = staff_on_job.sys_dept_id |
| | | LEFT JOIN staff_contract as t1 ON t1.staff_on_job_id = staff_on_job.id |
| | | where 1=1 |
| | | WHERE 1=1 |
| | | <if test="staffOnJob.staffState != null"> |
| | | AND staff_state = #{staffOnJob.staffState} |
| | | </if> |
| | |
| | | <if test="staffOnJob.entryDateEnd != null and staffOnJob.entryDateEnd != '' "> |
| | | AND contract_expire_time <= DATE_FORMAT(#{staffOnJob.entryDateEnd},'%Y-%m-%d') |
| | | </if> |
| | | GROUP BY staff_on_job.id |
| | | </select> |
| | | <select id="staffOnJobList" resultType="com.ruoyi.staff.dto.StaffOnJobDto"> |
| | | SELECT |
| | |
| | | <result column="update_time" property="updateTime" /> |
| | | <result column="drag_sort" property="dragSort" /> |
| | | <result column="is_quality" property="isQuality" /> |
| | | <result column="type" property="type" /> |
| | | <result column="create_user" property="createUser" /> |
| | | <result column="dept_id" property="deptId" /> |
| | | </resultMap> |