| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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.mapper.SupplierManageMapper; |
| | | import com.ruoyi.basic.pojo.SupplierManage; |
| | | 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.RestController; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import java.io.IOException; |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.Base64; |
| | | 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.Map; |
| | | import java.util.NoSuchElementException; |
| | | import java.util.UUID; |
| | | |
| | | @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 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 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, |
| | | 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.supplierManageMapper = supplierManageMapper; |
| | | this.purchaseVisionStreamingChatModel = purchaseVisionStreamingChatModel; |
| | | } |
| | | |
| | | @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)); |
| | | } |
| | | |
| | | @Operation(summary = "éè´å¤æä»¶åæ") |
| | | @PostMapping(value = "/analyze-files", consumes = "multipart/form-data", produces = "text/stream;charset=utf-8") |
| | | 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 |
| | | : "请åæè¿äºéè´æä»¶ï¼æåå¯ç¨äºä¸å¡å¤ççæ°æ®ï¼å¹¶æ´çæå¾
客æ·ç¡®è®¤çæ ¼å¼"; |
| | | |
| | | 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, 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)); |
| | | } |
| | | |
| | | @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)); |
| | | } |
| | | } |
| | | |
| | | @Operation(summary = "éè´ä¼è¯å表") |
| | | @GetMapping("/history/sessions") |
| | | public AjaxResult listSessions() { |
| | | return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | @Operation(summary = "éè´ä¼è¯æ¶æ¯") |
| | | @GetMapping("/history/messages/{memoryId}") |
| | | public AjaxResult listMessages(@PathVariable String memoryId) { |
| | | return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | @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 Flux<String> chatWithPurchaseVisionModel(String memoryId, String userPrompt, MultipartFile[] files) { |
| | | return Flux.create(sink -> { |
| | | 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) |
| | | ); |
| | | purchaseVisionStreamingChatModel.chat(messages, new StreamingChatResponseHandler() { |
| | | @Override |
| | | public void onPartialResponse(String partialResponse) { |
| | | sink.next(partialResponse); |
| | | } |
| | | |
| | | @Override |
| | | public void onCompleteResponse(ChatResponse completeResponse) { |
| | | 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 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); |
| | | } |
| | | } |