From 69dc6b16ef04bdfbfa65f77c169c0847dc7e65c2 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 06 五月 2026 16:26:43 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_New_pro' into dev_New_pro
---
src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java | 803 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 802 insertions(+), 1 deletions(-)
diff --git a/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java b/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
index f3e5aec..2bb0625 100644
--- a/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
+++ b/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
@@ -1,20 +1,45 @@
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;
@@ -22,31 +47,73 @@
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) {
+ 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 = "閲囪喘瀵硅瘽")
@@ -81,6 +148,84 @@
.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() {
@@ -99,4 +244,660 @@
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銆乯pg銆乯peg銆亀ebp 鎴� 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. 濡傛灉鍙垽鏂负閲囪喘鍙拌处锛宐usinessType 浣跨敤 purchase_ledger锛宲ayload.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銆乤pproverId
+ - missingFields 鍙~鍐欎笟鍔″繀濉絾鏃犳硶璇嗗埆鐨勫瓧娈碉紝涓嶈鎶� PurchaseLedgerDto 鐨勬墍鏈夌┖瀛楁閮藉垪涓虹己澶憋紱缂哄け椤瑰繀椤诲啓涓枃锛屼緥濡傗�滀緵搴斿晢鍚嶇О鈥濃�滃惈绋庡崟浠封�濓紝涓嶈鍐� supplierId銆乼axInclusiveUnitPrice
+ - 閲囪喘鍙拌处涓昏〃蹇呭~瀛楁浠呮寜杩欎簺鍒ゆ柇: purchaseContractNumber銆乻upplierName 鎴� supplierId
+ - productData 姣忔潯浜у搧蹇呭~瀛楁: productCategory銆乻pecificationModel銆乽nit銆乹uantity銆乼axInclusiveUnitPrice 鎴� 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. 濡傛灉鍙垽鏂负浠樻鐧昏锛宐usinessType 浣跨敤 payment_registration锛宲ayload.records 涓轰粯娆剧櫥璁版暟缁勶紝瀛楁灏介噺鍖呭惈 purchaseLedgerId銆乻alesLedgerProductId銆乧urrentPaymentAmount銆乸aymentMethod銆乸aymentDate銆�
+ 5. 濡傛灉鍙垽鏂负閲囪喘閫�璐э紝businessType 浣跨敤 purchase_return_order锛宲ayload 鎸� 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", "渚涘簲鍟唅d", "渚涘簲鍟咺D", "渚涘簲鍟嗗悕绉癷d", "渚涘簲鍟嗗悕绉癐D");
+ putDtoFieldIfPresent(source, target, "supplierName", "渚涘簲鍟�", "渚涘簲鍟嗗悕绉�");
+ putDtoFieldIfPresent(source, target, "isWhite", "鏄惁鐧藉悕鍗�");
+ putDtoFieldIfPresent(source, target, "recorderId", "褰曞叆浜篿d", "褰曞叆浜篒D", "褰曞叆浜哄鍚峣d", "褰曞叆浜哄鍚岻D");
+ putDtoFieldIfPresent(source, target, "recorderName", "褰曞叆浜�", "褰曞叆浜哄鍚�");
+ putDtoFieldIfPresent(source, target, "salesContractNo", "salesContractNumber", "閿�鍞悎鍚屽彿", "閿�鍞崟鍙�", "閿�鍞鍗曞彿");
+ putDtoFieldIfPresent(source, target, "salesContractNoId", "閿�鍞悎鍚屽彿id", "閿�鍞悎鍚屽彿ID", "閿�鍞崟鍙穒d", "閿�鍞崟鍙稩D");
+ 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", "閿�鍞彴璐d", "閿�鍞彴璐D", "鍏宠仈閿�鍞彴璐︿富琛ㄤ富閿�");
+ 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", "涓氬姟鍛榠d", "涓氬姟鍛業D");
+ 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 "澶勭悊澶辫触锛氬綍鍏ユ棩鏈熸牸寮忎笉姝g‘锛岃浣跨敤 yyyy-MM-dd锛屼緥濡� 2026-04-30";
+ }
+ if (message.contains("supplier")) {
+ return "澶勭悊澶辫触锛氫緵搴斿晢淇℃伅涓嶅畬鏁达紝璇风‘璁や緵搴斿晢鍚嶇О鎴栦緵搴斿晢ID";
+ }
+ if (message.contains("SQL") || message.contains("java.") || message.contains("Exception")) {
+ return "澶勭悊澶辫触锛氱‘璁ゆ暟鎹笉瀹屾暣鎴栨牸寮忎笉姝g‘锛岃妫�鏌ュ繀濉瓧娈靛悗閲嶈瘯";
+ }
+ return "澶勭悊澶辫触锛�" + message;
+ }
+
+ private AjaxResult fillSupplierIdByName(PurchaseLedgerDto dto) {
+ if (dto.getSupplierId() != null) {
+ return null;
+ }
+ if (!StringUtils.hasText(dto.getSupplierName())) {
+ return AjaxResult.error("渚涘簲鍟咺D涓嶈兘涓虹┖锛涙湭璇嗗埆鍒颁緵搴斿晢鍚嶇О锛屾棤娉曡嚜鍔ㄥ尮閰嶄緵搴斿晢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() + "锛岃鍏堢淮鎶や緵搴斿晢鎴栨墜鍔ㄩ�夋嫨渚涘簲鍟咺D");
+ }
+ 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);
+ }
}
--
Gitblit v1.9.3