From b1f41c066b0b7c38faffddbfe73b9be8d0bd508e Mon Sep 17 00:00:00 2001
From: yxh <172933527@qq.com>
Date: 星期日, 08 十一月 2026 23:42:09 +0800
Subject: [PATCH] 修改了增值税城建税

---
 ruoyi-project/src/main/resources/mapper/project/CasePanoramaMapper.xml                 |   76 ++++++++
 ruoyi-project/src/main/java/com/ruoyi/project/mapper/CasePanoramaMapper.java           |   28 +++
 ruoyi-project/src/main/java/com/ruoyi/project/service/ICasePanoramaService.java        |   25 ++
 ruoyi-framework/src/main/java/com/ruoyi/framework/config/JacksonDateConfig.java        |  272 ++++++++++++++++++++++++++++++
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/CasePanoramaController.java |   97 ++++++++++
 5 files changed, 498 insertions(+), 0 deletions(-)

diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/CasePanoramaController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/CasePanoramaController.java
new file mode 100644
index 0000000..150837c
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/CasePanoramaController.java
@@ -0,0 +1,97 @@
+package com.ruoyi.web.controller.project;
+
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.project.domain.CasePanorama;
+import com.ruoyi.project.domain.CasePanoramaReq;
+import com.ruoyi.project.service.ICasePanoramaService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 妗堜緥鍏ㄦ櫙
+ *
+ * @author ruoyi
+ * @date 2026-10-28
+ */
+@Slf4j
+@Api("妗堜緥鍏ㄦ櫙")
+@RestController
+@RequestMapping("/project/casePanorama")
+public class CasePanoramaController extends BaseController {
+
+    @Autowired
+    private ICasePanoramaService casePanoramaService;
+
+    /**
+     * 鏌ヨ妗堜緥鍏ㄦ櫙
+     */
+    @ApiOperation("鏌ヨ妗堜緥鍏ㄦ櫙")
+    @GetMapping("/list")
+    public TableDataInfo list(CasePanoramaReq req) {
+        startPage();
+        return getDataTable(casePanoramaService.selectCasePanoramaList(req));
+    }
+
+    /**
+     * 瀵煎嚭妗堜緥鍏ㄦ櫙
+     */
+    @ApiOperation("瀵煎嚭妗堜緥鍏ㄦ櫙")
+    @Log(title = "妗堜緥鍏ㄦ櫙", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CasePanoramaReq req) {
+        List<CasePanorama> list = casePanoramaService.selectCasePanoramaList(req);
+        for (CasePanorama row : list) {
+            blankZero(row);
+        }
+        ExcelUtil<CasePanorama> util = new ExcelUtil<CasePanorama>(CasePanorama.class);
+        return util.exportExcel(list, "妗堜緥鍏ㄦ櫙鏁版嵁");
+    }
+
+    /**
+     * 涓氬姟缁勪笅鎷夐�夐」
+     */
+    @ApiOperation("涓氬姟缁勪笅鎷夐�夐」")
+    @GetMapping("/deptOptions")
+    public AjaxResult deptOptions() {
+        return AjaxResult.success(casePanoramaService.selectDeptNames());
+    }
+
+    /**
+     * 閲戦 0 瀵煎嚭涓虹┖
+     */
+    private void blankZero(CasePanorama row) {
+        row.setTotalCost(blankZero(row.getTotalCost()));
+        row.setDonorFamilyCost(blankZero(row.getDonorFamilyCost()));
+        row.setMedCost(blankZero(row.getMedCost()));
+        row.setExpertCost(blankZero(row.getExpertCost()));
+        row.setItemCode34(blankZero(row.getItemCode34()));
+        row.setItemCode42(blankZero(row.getItemCode42()));
+        row.setItemCode46(blankZero(row.getItemCode46()));
+        row.setItemCode50(blankZero(row.getItemCode50()));
+        row.setItemCode44(blankZero(row.getItemCode44()));
+        row.setItemCode47(blankZero(row.getItemCode47()));
+        row.setItemCode48(blankZero(row.getItemCode48()));
+        row.setItemCode21(blankZero(row.getItemCode21()));
+        row.setItemCode12(blankZero(row.getItemCode12()));
+        row.setItemCode17(blankZero(row.getItemCode17()));
+        row.setItemCode23(blankZero(row.getItemCode23()));
+        row.setItemCode25(blankZero(row.getItemCode25()));
+    }
+
+    private BigDecimal blankZero(BigDecimal value) {
+        return (value == null || value.compareTo(BigDecimal.ZERO) == 0) ? null : value;
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/JacksonDateConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/JacksonDateConfig.java
new file mode 100644
index 0000000..206a4e3
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/JacksonDateConfig.java
@@ -0,0 +1,272 @@
+package com.ruoyi.framework.config;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.DeserializationConfig;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
+import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
+import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.IOException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.TimeZone;
+
+/**
+ * 鏃堕棿鏍煎紡鍏ㄥ眬閰嶇疆锛氭帴鍙e嚭鍙備竴寰嬬簿纭埌鍒嗭紙{@code yyyy-MM-dd HH:mm}锛夛紝鍏ュ弬瀹芥澗鍏煎甯�/涓嶅甫绉掋��
+ *
+ * <p><b>涓轰粈涔堢敤鍏ㄥ眬閰嶇疆鑰屼笉鏄�愬瓧娈垫敼娉ㄨВ锛�</b>绯荤粺閲�
+ * {@code @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")} 鏈� 182 澶勩�亄@code @DateTimeFormat} 73 澶勶紝
+ * 閫愪釜鏀规棦瀹规槗婕忥紙婕忎竴澶勫氨鏄彁浜� 400锛夛紝涔熻鐩栦笉鍒版湭鍔犳敞瑙g殑 {@code Date} 瀛楁銆�
+ * 杩欓噷鐢� Jackson 鐨� {@link BeanSerializerModifier} / {@link BeanDeserializerModifier}
+ * 鍦�<b>涓嶆敼浠讳綍娉ㄨВ</b>鐨勫墠鎻愪笅缁熶竴鏍煎紡鍖栧彛寰勩��
+ *
+ * <p><b>瑙勫垯锛�</b>
+ * <ol>
+ *   <li>搴忓垪鍖栵細鎵�鏈� {@code Date} 杈撳嚭 {@code yyyy-MM-dd HH:mm}锛堟部鐢�
+ *       {@link ApplicationConfig} 閰嶇疆鐨� JVM 榛樿鏃跺尯锛涘瓧娈典笂鏄惧紡澹版槑浜�
+ *       {@code @JsonFormat(timezone=...)} 鐨勪粛鎸夊叾鏃跺尯锛夛紱</li>
+ *   <li>鍙嶅簭鍒楀寲锛氬吋瀹� {@code yyyy-MM-dd HH:mm:ss} / {@code yyyy-MM-dd HH:mm} /
+ *       {@code yyyy-MM-dd} / 鏂滄潬鍒嗛殧 / ISO8601 / 鏃堕棿鎴� 鈥斺�� 杩欐牱鍓嶇鍥炲~"鏃犵"鐨勫�煎師鏍锋彁浜や篃涓嶄細 400锛�</li>
+ *   <li>杩愮淮鏃ュ織锛堟搷浣滄棩蹇�/鐧诲綍鏃ュ織/瀹氭椂浠诲姟鏃ュ織锛�<b>淇濈暀绉�</b>锛屼究浜庢帓鏌ラ棶棰樸��</li>
+ * </ol>
+ *
+ * <p><b>涓嶅奖鍝嶇殑鑼冨洿锛�</b>鏁版嵁搴撳瓨鍌紙浠嶄负 datetime锛岀浣嶇収甯镐繚鐣欙級銆�
+ * Excel 瀵煎叆瀵煎嚭锛堣蛋 {@code @Excel.dateFormat}锛屾湭鏀瑰姩锛夈��
+ * 鑷磋繙鍗忓悓鎺ㄩ�佹姤鏂囷紙鑷缓 {@code SimpleDateFormat}锛屾湭鏀瑰姩锛夈��
+ * 閽夐拤娑堟伅鏃堕棿锛坽@code FundNotifyMessageBuilder} 鑷缓鏍煎紡锛夈��
+ *
+ * @author ruoyi
+ */
+@Configuration
+public class JacksonDateConfig {
+
+    /** 鏃犵鏍煎紡 */
+    private static final String PATTERN_NO_SECONDS = "yyyy-MM-dd HH:mm";
+    /** 鏈夌鏍煎紡锛堣繍缁存棩蹇楃敤锛� */
+    private static final String PATTERN_WITH_SECONDS = "yyyy-MM-dd HH:mm:ss";
+
+    /** 淇濈暀绉掔殑绫伙細杩愮淮鏃ュ織锛岀瀵规帓鏌ユ湁鐢� */
+    private static final Set<String> KEEP_SECONDS_CLASSES = new HashSet<String>(Arrays.asList(
+            "com.ruoyi.system.domain.SysOperLog",
+            "com.ruoyi.system.domain.SysLogininfor",
+            "com.ruoyi.quartz.domain.SysJobLog"
+    ));
+
+    /** 鍙嶅簭鍒楀寲灏濊瘯椤哄簭锛氫粠鏈�鍏蜂綋鍒版渶瀹芥澗 */
+    private static final String[] PARSE_PATTERNS = new String[]{
+            "yyyy-MM-dd HH:mm:ss.SSS",
+            "yyyy-MM-dd HH:mm:ss",
+            "yyyy-MM-dd HH:mm",
+            "yyyy-MM-dd'T'HH:mm:ss.SSS",
+            "yyyy-MM-dd'T'HH:mm:ss",
+            "yyyy-MM-dd'T'HH:mm",
+            "yyyy/MM/dd HH:mm:ss",
+            "yyyy/MM/dd HH:mm",
+            "yyyy-MM-dd",
+            "yyyy/MM/dd",
+            "yyyyMMddHHmmss",
+            "yyyyMMdd"
+    };
+
+    /**
+     * 2026-10-15 鏀归�狅細涓嶅啀鐢� {@code Jackson2ObjectMapperBuilderCustomizer} + {@code modulesToInstall}銆�
+     *
+     * <p><b>涓轰粈涔堟敼锛�</b>{@code Jackson2ObjectMapperBuilder.modulesToInstall(...)} 鐨勮涔夋槸
+     * "璁剧疆"妯″潡鍒楄〃锛堜笉鏄拷鍔狅級锛岃�� Spring Boot 鑷韩涔熺敤鍚屼竴鏂规硶娉ㄥ唽瀹冭嚜甯︾殑妯″潡锛圝avaTime 绛夛級锛�
+     * 涓や釜瀹氬埗鍣ㄤ簰鐩歌鐩栵紝璋佸悗鎵ц璋佺殑鍒楄〃鐢熸晥銆傚疄娴嬪悗鏋滐細鏈ā鍧楄涓㈡帀 鈥斺��
+     * 琛ㄧ幇涓�"绫诲湪鍖呴噷銆乥ean 涔熷垱寤轰簡锛屼絾 Date 鐨勫鏉捐В鏋愪笌鍘荤搴忓垪鍖栭兘涓嶈捣浣滅敤"锛�
+     * 鍓嶇鎸� {@code yyyy-MM-dd} 鎻愪氦鏃剁洿鎺� 400锛圕annot deserialize ... expected format yyyy-MM-dd HH:mm:ss锛夈��
+     *
+     * <p>鏀逛负鎶婃ā鍧楀0鏄庢垚 {@code SimpleModule} bean锛歋pring Boot 閫氳繃 {@code ObjectProvider<Module>}
+     * 缁熶竴鏀堕泦鎵�鏈� Module bean 骞朵竴娆℃�у畨瑁咃紝涓庡畾鍒跺櫒鐨勬墽琛岄『搴忔棤鍏炽��
+     */
+    @Bean
+    public SimpleModule dateNoSecondsModule() {
+        SimpleModule module = new SimpleModule("dateNoSecondsModule");
+        module.addSerializer(Date.class, new NoSecondsDateSerializer());
+        module.setSerializerModifier(new NoSecondsDateSerializerModifier());
+        module.setDeserializerModifier(new LenientDateDeserializerModifier());
+        // 鍚姩鑷瘉锛氳繖鏉℃棩蹇楀嚭鐜板嵆璇存槑妯″潡宸茶鍒涘缓骞朵細琚� Spring Boot 瀹夎
+        LoggerFactory.getLogger(JacksonDateConfig.class).info(
+                "[JacksonDateConfig] 宸插惎鐢細Date 鍑哄弬鍘荤({})锛屽叆鍙傚鏉惧吋瀹�({})",
+                PATTERN_NO_SECONDS, String.join(" / ", PARSE_PATTERNS));
+        return module;
+    }
+
+    /**
+     * 搴忓垪鍖栧櫒锛氭寜鍒嗛挓杈撳嚭銆�
+     * 鍙綔鐢ㄤ簬搴忓垪鍖栵紝<b>涓嶅奖鍝嶅叆鍙傝В鏋�</b>銆�
+     */
+    static class NoSecondsDateSerializer extends JsonSerializer<Date> implements ContextualSerializer {
+
+        private final DateTimeFormatter formatter;
+
+        NoSecondsDateSerializer() {
+            this.formatter = null;
+        }
+
+        private NoSecondsDateSerializer(TimeZone timeZone, boolean keepSeconds) {
+            this.formatter = DateTimeFormatter
+                    .ofPattern(keepSeconds ? PATTERN_WITH_SECONDS : PATTERN_NO_SECONDS)
+                    .withZone((timeZone == null ? TimeZone.getDefault() : timeZone).toZoneId());
+        }
+
+        @Override
+        public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
+            TimeZone timeZone = resolveTimeZone(prov.getConfig().getTimeZone(),
+                    property == null ? null : property.getMember());
+            return new NoSecondsDateSerializer(timeZone, isKeepSeconds(property == null ? null : property.getMember()));
+        }
+
+        @Override
+        public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+            if (value == null) {
+                gen.writeNull();
+                return;
+            }
+            if (formatter != null) {
+                gen.writeString(formatter.format(value.toInstant()));
+                return;
+            }
+            // 鍏滃簳锛氭湭琚� contextualize 鏃舵寜褰撳墠閰嶇疆鏃跺尯銆佹棤绉掕緭鍑�
+            gen.writeString(DateTimeFormatter.ofPattern(PATTERN_NO_SECONDS)
+                    .withZone(serializers.getConfig().getTimeZone().toZoneId())
+                    .format(value.toInstant()));
+        }
+    }
+
+    /**
+     * 璁� Bean 灞炴�т笂鐨勬棩鏈熶篃璧颁笂闈㈢殑搴忓垪鍖栧櫒銆�
+     * 瀛楁绾� {@code @JsonFormat} 鐢熸垚鐨勫簭鍒楀寲鍣ㄤ紭鍏堢骇鏇撮珮锛屽繀椤诲湪杩欓噷鏇挎崲鎺夛紝鍚﹀垯娉ㄨВ瀛楁浠嶅甫绉掋��
+     */
+    static class NoSecondsDateSerializerModifier extends BeanSerializerModifier {
+        @Override
+        @SuppressWarnings({"unchecked", "rawtypes"})
+        public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
+                                                         List<BeanPropertyWriter> beanProperties) {
+            boolean keepSeconds = KEEP_SECONDS_CLASSES.contains(beanDesc.getBeanClass().getName());
+            for (BeanPropertyWriter writer : beanProperties) {
+                if (Date.class.isAssignableFrom(writer.getType().getRawClass())) {
+                    TimeZone timeZone = resolveTimeZone(config.getTimeZone(), writer.getMember());
+                    writer.assignSerializer((JsonSerializer) new NoSecondsDateSerializer(timeZone, keepSeconds));
+                }
+            }
+            return beanProperties;
+        }
+    }
+
+    /**
+     * 鍏ュ弬瑙f瀽锛氬吋瀹瑰甫绉�/涓嶅甫绉�/绾棩鏈�/ISO/鏃堕棿鎴筹紝閬垮厤"鍑哄弬鏃犵銆佸洖濉悗鍐嶆彁浜�"瀵艰嚧 400銆�
+     */
+    static class LenientDateDeserializerModifier extends BeanDeserializerModifier {
+        @Override
+        @SuppressWarnings({"unchecked", "rawtypes"})
+        public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc,
+                                                     BeanDeserializerBuilder builder) {
+            Iterator<SettableBeanProperty> properties = builder.getProperties();
+            List<SettableBeanProperty> replaced = new ArrayList<SettableBeanProperty>();
+            while (properties.hasNext()) {
+                SettableBeanProperty property = properties.next();
+                if (property.getType() != null && Date.class.isAssignableFrom(property.getType().getRawClass())) {
+                    TimeZone timeZone = resolveTimeZone(config.getTimeZone(), property.getMember());
+                    replaced.add(property.withValueDeserializer(new LenientDateDeserializer(timeZone)));
+                }
+            }
+            // 涓嶈兘杈归亶鍘嗚竟鏀癸紝鍏堟敹闆嗗啀鏇挎崲
+            for (SettableBeanProperty property : replaced) {
+                builder.addOrReplaceProperty(property, true);
+            }
+            return builder;
+        }
+    }
+
+    /** 瀹芥澗鏃ユ湡瑙f瀽鍣� */
+    static class LenientDateDeserializer extends JsonDeserializer<Date> {
+
+        private final TimeZone timeZone;
+
+        LenientDateDeserializer(TimeZone timeZone) {
+            this.timeZone = timeZone == null ? TimeZone.getDefault() : timeZone;
+        }
+
+        @Override
+        public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+            if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+                return new Date(p.getLongValue());
+            }
+            String text = p.getValueAsString();
+            if (text == null || text.trim().isEmpty()) {
+                return null;
+            }
+            text = text.trim();
+            for (String pattern : PARSE_PATTERNS) {
+                SimpleDateFormat format = new SimpleDateFormat(pattern);
+                format.setLenient(false);
+                format.setTimeZone(timeZone);
+                ParsePosition position = new ParsePosition(0);
+                Date parsed = format.parse(text, position);
+                // 蹇呴』鏁翠覆琚秷璐癸紝鍚﹀垯 "yyyy-MM-dd" 浼氭妸 "2026-10-08 15:23" 鐨勬椂鍒嗛潤榛樹涪鎺�
+                if (parsed != null && position.getIndex() == text.length()) {
+                    return parsed;
+                }
+            }
+            try {
+                return Date.from(Instant.parse(text));
+            } catch (Exception ignore) {
+                // 钀藉埌涓嬮潰缁熶竴鎶ラ敊
+            }
+            throw ctxt.weirdStringException(text, Date.class,
+                    "鏃犳硶瑙f瀽鐨勬椂闂存牸寮忥紝鏀寔 yyyy-MM-dd[ HH:mm[:ss]]");
+        }
+    }
+
+    /** 灞炴�т笂鏄惧紡澹版槑鐨勬椂鍖轰紭鍏堬紝鍚﹀垯鐢ㄥ叏灞�閰嶇疆锛堝嵆 JVM 榛樿鏃跺尯锛� */
+    private static TimeZone resolveTimeZone(TimeZone fallback, AnnotatedMember member) {
+        if (member != null) {
+            try {
+                JsonFormat jsonFormat = member.getAnnotation(JsonFormat.class);
+                if (jsonFormat != null && jsonFormat.timezone() != null
+                        && !jsonFormat.timezone().isEmpty()
+                        && !"##default".equals(jsonFormat.timezone())) {
+                    return TimeZone.getTimeZone(jsonFormat.timezone());
+                }
+            } catch (Exception ignore) {
+                // 鍙栦笉鍒版敞璁版椂鐢ㄥ叏灞�鏃跺尯
+            }
+        }
+        return fallback == null ? TimeZone.getDefault() : fallback;
+    }
+
+    /** 杩愮淮鏃ュ織绫讳繚鐣欑 */
+    private static boolean isKeepSeconds(AnnotatedMember member) {
+        return member != null && KEEP_SECONDS_CLASSES.contains(member.getDeclaringClass().getName());
+    }
+}
diff --git a/ruoyi-project/src/main/java/com/ruoyi/project/mapper/CasePanoramaMapper.java b/ruoyi-project/src/main/java/com/ruoyi/project/mapper/CasePanoramaMapper.java
new file mode 100644
index 0000000..fa67089
--- /dev/null
+++ b/ruoyi-project/src/main/java/com/ruoyi/project/mapper/CasePanoramaMapper.java
@@ -0,0 +1,28 @@
+package com.ruoyi.project.mapper;
+
+import com.ruoyi.project.domain.CasePanorama;
+import com.ruoyi.project.domain.CasePanoramaReq;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 妗堜緥鍏ㄦ櫙Mapper
+ *
+ * @author ruoyi
+ * @date 2026-10-28
+ */
+@Mapper
+public interface CasePanoramaMapper {
+
+    /**
+     * 鏌ヨ妗堜緥鍏ㄦ櫙
+     */
+    List<CasePanorama> selectCasePanoramaList(@Param("req") CasePanoramaReq req);
+
+    /**
+     * 鏌ヨ涓氬姟缁勯�夐」
+     */
+    List<String> selectDeptNames();
+}
diff --git a/ruoyi-project/src/main/java/com/ruoyi/project/service/ICasePanoramaService.java b/ruoyi-project/src/main/java/com/ruoyi/project/service/ICasePanoramaService.java
new file mode 100644
index 0000000..42c283e
--- /dev/null
+++ b/ruoyi-project/src/main/java/com/ruoyi/project/service/ICasePanoramaService.java
@@ -0,0 +1,25 @@
+package com.ruoyi.project.service;
+
+import com.ruoyi.project.domain.CasePanorama;
+import com.ruoyi.project.domain.CasePanoramaReq;
+
+import java.util.List;
+
+/**
+ * 妗堜緥鍏ㄦ櫙Service
+ *
+ * @author ruoyi
+ * @date 2026-10-28
+ */
+public interface ICasePanoramaService {
+
+    /**
+     * 鏌ヨ妗堜緥鍏ㄦ櫙
+     */
+    List<CasePanorama> selectCasePanoramaList(CasePanoramaReq req);
+
+    /**
+     * 鏌ヨ涓氬姟缁勯�夐」
+     */
+    List<String> selectDeptNames();
+}
diff --git a/ruoyi-project/src/main/resources/mapper/project/CasePanoramaMapper.xml b/ruoyi-project/src/main/resources/mapper/project/CasePanoramaMapper.xml
new file mode 100644
index 0000000..f1ce44a
--- /dev/null
+++ b/ruoyi-project/src/main/resources/mapper/project/CasePanoramaMapper.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.project.mapper.CasePanoramaMapper">
+
+    <sql id="casePanoramaColumns">
+        id,
+        DonorName AS donorName,
+        ReportTime AS reportTime,
+        deptname AS deptName,
+        ReporterName AS reporterName,
+        Dontiontermination AS terminationFlag,
+        workflow AS workflow,
+        COALESCE(dd.dict_label, CAST(workflow AS CHAR)) AS workflowName,
+        DonateTime AS donateTime,
+        DonationHospital AS donationHospital,
+        OrganName AS organName,
+        OrganProcurement AS organProcurement,
+        OrganTransplantation AS organTransplantation,
+        COALESCE(DonorFamilysCost, 0) + COALESCE(medCost, 0) + COALESCE(ExpertCost, 0) + COALESCE(ItemCode3_4, 0) AS totalCost,
+        DonorFamilysCost AS donorFamilyCost,
+        medCost AS medCost,
+        ExpertCost AS expertCost,
+        ItemCode3_4 AS itemCode34,
+        ItemCode42 AS itemCode42,
+        ItemCode46 AS itemCode46,
+        ItemCode50 AS itemCode50,
+        ItemCode44 AS itemCode44,
+        ItemCode47 AS itemCode47,
+        ItemCode48 AS itemCode48,
+        ItemCode21 AS itemCode21,
+        ItemCode12 AS itemCode12,
+        ItemCode17 AS itemCode17,
+        ItemCode23 AS itemCode23,
+        ItemCode25 AS itemCode25
+    </sql>
+
+    <select id="selectCasePanoramaList" resultType="com.ruoyi.project.domain.CasePanorama">
+        SELECT
+        <include refid="casePanoramaColumns"/>
+        FROM v_donationfeestat v
+        LEFT JOIN (
+            SELECT dict_value, MAX(dict_label) AS dict_label
+            FROM sys_dict_data
+            WHERE dict_type = 'sys_donornode'
+            GROUP BY dict_value
+        ) dd ON dd.dict_value COLLATE utf8mb4_general_ci = CAST(v.workflow AS CHAR) COLLATE utf8mb4_general_ci
+        <where>
+            <if test="req.donorName != null and req.donorName != ''">
+                AND DonorName LIKE CONCAT('%', #{req.donorName}, '%')
+            </if>
+            <if test="req.donateTimeBegin != null and req.donateTimeBegin != ''">
+                AND DonateTime &gt;= #{req.donateTimeBegin}
+            </if>
+            <if test="req.donateTimeEnd != null and req.donateTimeEnd != ''">
+                AND DonateTime &lt;= #{req.donateTimeEnd}
+            </if>
+            <if test="req.deptName != null and req.deptName != ''">
+                AND deptname = #{req.deptName}
+            </if>
+            <if test="req.reporterName != null and req.reporterName != ''">
+                AND ReporterName LIKE CONCAT('%', #{req.reporterName}, '%')
+            </if>
+        </where>
+        ORDER BY ReportTime DESC, id DESC
+    </select>
+
+    <select id="selectDeptNames" resultType="java.lang.String">
+        SELECT DISTINCT deptname
+        FROM v_donationfeestat
+        WHERE deptname IS NOT NULL AND deptname != ''
+        ORDER BY deptname
+    </select>
+
+</mapper>

--
Gitblit v1.9.3