Sfoglia il codice sorgente

Merge branch 'master' of http://47.107.245.0:3000/xiongxing/kyj-yanglao-web-new

chenjun 1 settimana fa
parent
commit
2040caa1ea

+ 70 - 0
src/api/system/commonSeal/index.ts

@@ -0,0 +1,70 @@
+import request from '@/config/axios'
+
+/** 公章信息 */
+export interface CommonSealVO {
+  /** id,修改 / 删除时必传 */
+  id?: number
+  /** 类型,取值见字典 common_seal(deposit_seal 押金公章 / expense_order_seal 账单公章) */
+  type?: string
+  /** 公章 url */
+  url?: string
+  /** 机构 id */
+  tenantId?: number
+  createTime?: string
+  updateTime?: string
+  creator?: string
+  updater?: string
+}
+
+/** 公章列表查询参数 */
+export interface CommonSealListReqVO {
+  type?: string
+  /** 机构 id,不传则默认当前登录租户 */
+  tenantId?: number
+}
+
+/** 公章类型取值(对应字典 common_seal) */
+export const SEAL_TYPE = {
+  /** 押金公章 */
+  DEPOSIT: 'deposit_seal',
+  /** 账单公章 */
+  EXPENSE_ORDER: 'expense_order_seal'
+}
+
+/** 获得公章列表 */
+export const getCommonSealList = (params?: CommonSealListReqVO) => {
+  return request.get({ url: '/system/common-seal/list', params })
+}
+
+/** 获得公章 */
+export const getCommonSeal = (id: number) => {
+  return request.get({ url: '/system/common-seal/get?id=' + id })
+}
+
+/** 创建公章 */
+export const createCommonSeal = (data: CommonSealVO) => {
+  return request.post({ url: '/system/common-seal/create', data })
+}
+
+/** 更新公章 */
+export const updateCommonSeal = (data: CommonSealVO) => {
+  return request.put({ url: '/system/common-seal/update', data })
+}
+
+/** 删除公章 */
+export const deleteCommonSeal = (id: number) => {
+  return request.delete({ url: '/system/common-seal/delete?id=' + id })
+}
+
+/**
+ * 按类型取当前机构的公章 url(取第一条)
+ * 容错处理:接口异常时返回空字符串,避免影响打印 / 预览主流程
+ */
+export const getSealUrlByType = async (type: string, tenantId?: number): Promise<string> => {
+  try {
+    const list: CommonSealVO[] = (await getCommonSealList({ type, tenantId })) || []
+    return list[0]?.url || ''
+  } catch {
+    return ''
+  }
+}

+ 7 - 7
src/components/Table2/index.vue

@@ -459,14 +459,14 @@ const userStore = useUserStore()
 const settingStore = useSettingStore()
 const radioId = ref() // 单选表格
 
-//续签提前 16天
 const isDisabledRenewal = (row) => {
-  if (!row.expireTime) return false
-  const expireDate = new Date(row.expireTime)
-  const halfMonthLater = new Date()
-  halfMonthLater.setMonth(halfMonthLater.getMonth() + 2)
-  console.log('AAA')
-  return expireDate > halfMonthLater && (row.isRenew || 0) == 0
+  // 暂时不做“到期前两个月内才可续签”的时间限制,只看 isRenew
+  // if (!row.expireTime) return false
+  // const expireDate = new Date(row.expireTime)
+  // const halfMonthLater = new Date()
+  // halfMonthLater.setMonth(halfMonthLater.getMonth() + 2)
+  // return expireDate > halfMonthLater && (row.isRenew || 0) == 0
+  return (row.isRenew || 0) != 0
 }
 
 const props = defineProps({

+ 9 - 0
src/store/modules/setting.ts

@@ -12,6 +12,8 @@ export const useSettingStore = defineStore('setting', {
     admissionBill: -1, // 入院账单(应收1,四舍五入3,向上取整2, 四舍五入取整4)
     // 消费券规则
     couponRuleValue: '',
+    // 长护险规则
+    longCareRuleValue: ''
   }),
   getters: {
     getIsLoaded(): boolean {
@@ -35,6 +37,9 @@ export const useSettingStore = defineStore('setting', {
     getCouponRuleValue(): string{ // 消费券规则
       return this.couponRuleValue
     },
+    getLongCareRuleValue(): string{ //长护险规则
+      return this.longCareRuleValue
+    }
   },
   actions: {
     async getParamsConfig(){
@@ -54,6 +59,10 @@ export const useSettingStore = defineStore('setting', {
       if(item3){
         this.couponRuleValue = item3.value
       }
+      const item4 = res.find(item=>item.configName == '长护险抹零设置')
+      if(item4){
+        this.longCareRuleValue = item4.value
+      }
       this.isLoaded = true
     }
   }

+ 1 - 0
src/utils/dict.ts

@@ -423,6 +423,7 @@ export enum DICT_TYPE {
   PAY_TYPE = 'pay_type', // 支付状态
   PUSH_STATUS = 'push_status', // 推送状态
   BILL_TYPE = 'bill_type', // 账单类型
+  COMMON_SEAL = 'common_seal', // 公章类型(押金公章 / 账单公章)
 
   // 床位类型
   PRIVATE_ROOM = 'private_room', // 床位

+ 14 - 8
src/views/elderly/fee/bill-pay/Form.vue

@@ -572,7 +572,6 @@ const route = useRoute()
 const router = useRouter()
 const isEdit = ref(false)
 const roundDown = ref(0)
-
 // 取整金额
 const rounding = computed(()=>{
   return settingStore.getRounding
@@ -588,6 +587,11 @@ const couponRuleValue = computed(()=>{
   return settingStore.getCouponRuleValue
 })
 
+// 长护险规则
+const longCareRuleValue = computed(()=>{
+  return settingStore.getLongCareRuleValue
+})
+
 const tTitle = computed(() => {
   const arr = dataForm.value.billingMonth ? dataForm.value.billingMonth.split('-') : ''
   if (arr.length) {
@@ -629,13 +633,15 @@ const stageExpandSubtotalAmount = stageSubtotalAmount
 
 const getReceivableAmount = (item) => {
   if (item?.expenseSource == 'consumer_voucher') {
-    const ruleValue = couponRuleValue.value
-    if (ruleValue == 2 || ruleValue == '2') {
+    if (couponRuleValue.value == 2 || couponRuleValue.value == '2') {
       return Number(formatRound(item.totalAmount || 0))
     }
     return Number(formatNum(item.totalAmount || 0))
   }
   if (item?.expenseSource == 'long_term_care_insurance') {
+    if (longCareRuleValue.value == 2 || longCareRuleValue.value == '2') {
+      return Number(formatRound(item.totalAmount || 0))
+    }
     return Number(item.totalAmount || 0)
   }
   if (dataForm.value.type == 1 && item?.isMonthlyExpense == 1) {
@@ -788,7 +794,7 @@ const handleMonthSelectionChange = (val) => {
   val.map((item) => {// 入院账单固定费用有设置向上取整
     if(item.isShow){
       if(dataForm.value.type == '1'){
-        if(item.expenseSource == 'consumer_voucher'){
+        if(item.expenseSource == 'consumer_voucher' || item.expenseSource == 'long_term_care_insurance'){
           total += formatDecimal(getReceivableAmount(item) - (item.payAmount || 0)) as number
         }else if(settingStore.getAdmissionBill == 2){ // 向上取整
           total += formatDecimal(Number(formatCeil(item.totalAmount - (item.payAmount || 0)))) as number
@@ -800,7 +806,7 @@ const handleMonthSelectionChange = (val) => {
           total += formatDecimal(formatNum(item.totalAmount - (item.payAmount || 0))) as number
         }
       }else{
-        if(item.expenseSource == 'consumer_voucher'){
+        if(item.expenseSource == 'consumer_voucher' || item.expenseSource == 'long_term_care_insurance'){
           total += formatDecimal(getReceivableAmount(item) - (item.payAmount || 0)) as number
         }else if(rounding.value){ // 账单取整
           total += formatDecimal(formatNum(item.roundAmount - (item.payAmount || 0))) as number
@@ -823,7 +829,7 @@ const handleDepositSelectionChange = (val) => {
   let total = 0
   val.map((item) => {
     if(item.isShow){
-      if(item.expenseSource == 'consumer_voucher'){
+      if(item.expenseSource == 'consumer_voucher' || item.expenseSource == 'long_term_care_insurance'){
         total += formatDecimal(getReceivableAmount(item) - (item.payAmount || 0)) as number
       }else if(rounding.value){ // 账单取整
         total += formatDecimal(formatNum(item.roundAmount - (item.payAmount || 0))) as number
@@ -845,7 +851,7 @@ const handleOneTimeSelectionChange = (val) => {
   let total = 0
   val.map((item) => {
     if(item.isShow){
-      if(item.expenseSource == 'consumer_voucher'){
+      if(item.expenseSource == 'consumer_voucher' || item.expenseSource == 'long_term_care_insurance'){
         total += formatDecimal(getReceivableAmount(item) - (item.payAmount || 0)) as number
       }else if(rounding.value){ // 账单取整
         total += formatDecimal(formatNum(item.roundAmount - (item.payAmount || 0))) as number
@@ -867,7 +873,7 @@ const handleStageExpandSelectionChange = (val) => {
   let total = 0
     val.map((item) => {
       if(item.isShow){
-        if(item.expenseSource == 'consumer_voucher'){
+        if(item.expenseSource == 'consumer_voucher' || item.expenseSource == 'long_term_care_insurance'){
           total += formatDecimal(getReceivableAmount(item) - (item.payAmount || 0)) as number
         }else if(rounding.value){ // 账单取整
           total += formatDecimal(formatNum(item.roundAmount - (item.payAmount || 0))) as number

+ 96 - 64
src/views/elderly/fee/bill-pay/preview.vue

@@ -229,8 +229,7 @@
 import { getExpenseOrderDetail } from '@/api/elderly/fee/bill-pay'
 import dayjs from 'dayjs'
 import { useUserStore } from '@/store/modules/user'
-import { getTenant } from '@/api/system/tenant'
-import { getTenantId } from '@/utils/auth'
+import { getSealUrlByType, SEAL_TYPE } from '@/api/system/commonSeal'
 import {
   fixedNum,
   formatCeil,
@@ -270,7 +269,7 @@ const longTermAmount = ref(0)
 const payText = ref('')
 const roundDown = ref(0)
 
-// 当前机构公章(来自租户接口 expenseCachetUrl);为空字符串时签章区不渲染
+// 当前机构账单公章(来自 /system/common-seal/list,type=expense_order_seal);为空字符串时签章区不渲染
 const sealUrl = ref('')
 
 const formatPayCreatedTime = (createdTime) => {
@@ -283,18 +282,18 @@ const formatPayCreatedTime = (createdTime) => {
 /** 打开弹窗 */
 const open = async (id, row) => {
   dialogVisible.value = true
-  // 并行:账单详情 + 当前机构公章(容错,租户接口失败不影响主流程)
-  const [res, tenant] = await Promise.all([
+  // 并行:账单详情 + 当前机构账单公章(容错,公章接口失败不影响主流程)
+  const [res, seal] = await Promise.all([
     getExpenseOrderDetail(id),
-    getTenant(getTenantId() as number).catch(() => null)
+    getSealUrlByType(SEAL_TYPE.EXPENSE_ORDER)
   ])
-  sealUrl.value = tenant?.expenseCachetUrl || ''
+  sealUrl.value = seal || ''
   let flag = false
   dataForm.value = res
   res.items = res.items.filter((item) => item.isShow == 1)
   res.items.map((item) => {
     if (item.expenseSource == 'long_term_care_insurance') {
-      longTermAmount.value += item.actualPrice
+      longTermAmount.value += formatLongCareAmount(item.actualPrice)
       item.payStatus = 0
       flag = true
       // 遍历长护险
@@ -350,16 +349,18 @@ const open = async (id, row) => {
     if (res.expenseSubsidyDO && res.expenseSubsidyDO.length) {
       let num = 0
       res.expenseSubsidyDO.map((item) => {
-        num += item.amount
+        // 按长护险抹零配置取数
+        const amount = formatLongCareAmount(item.amount)
+        num += amount
 
         dataForm.value.items.push({
           expenseSource: 'long_term_care_insurance',
           itemCategoryName: '长护险',
           itemName: '长护险抵扣',
           description: item.remarks ? item.remarks : item.month + '长护险抵扣',
-          roundAmount: -item.amount,
-          roundTwoDecimalAmount: -item.amount,
-          totalAmount: -item.amount,
+          roundAmount: -amount,
+          roundTwoDecimalAmount: -amount,
+          totalAmount: -amount,
           createdTime: item.month,
           payTime: '',
           payStatus: item.status
@@ -388,20 +389,20 @@ const handleClick = (val) => {
         if (rounding.value) {
           // 取整
           monthNum +=
-            item.expenseSource == 'consumer_voucher'
-              ? getConsumerVoucherAmountNumber(item)
+            hasSourceRule(item)
+              ? getSourceAmountNumber(item)
               : Number(item.roundAmount)
         } else if (fixed2Num.value) {
           // 四舍五入
           monthNum +=
-            item.expenseSource == 'consumer_voucher'
-              ? getConsumerVoucherAmountNumber(item)
+            hasSourceRule(item)
+              ? getSourceAmountNumber(item)
               : Number(item.roundTwoDecimalAmount)
         } else {
           // 应收
           monthNum +=
-            item.expenseSource == 'consumer_voucher'
-              ? getConsumerVoucherAmountNumber(item)
+            hasSourceRule(item)
+              ? getSourceAmountNumber(item)
               : Number(formatNum(item.totalAmount))
         }
       } else {
@@ -410,20 +411,20 @@ const handleClick = (val) => {
           if (rounding.value) {
             // 取整
             otherNum +=
-              item.expenseSource == 'consumer_voucher'
-                ? getConsumerVoucherAmountNumber(item)
+              hasSourceRule(item)
+                ? getSourceAmountNumber(item)
                 : Number(item.roundAmount)
           } else if (fixed2Num.value) {
             // 四舍五入
             otherNum +=
-              item.expenseSource == 'consumer_voucher'
-                ? getConsumerVoucherAmountNumber(item)
+              hasSourceRule(item)
+                ? getSourceAmountNumber(item)
                 : Number(item.roundTwoDecimalAmount)
           } else {
             // 应收
             otherNum +=
-              item.expenseSource == 'consumer_voucher'
-                ? getConsumerVoucherAmountNumber(item)
+              hasSourceRule(item)
+                ? getSourceAmountNumber(item)
                 : Number(formatNum(item.totalAmount))
           }
         }
@@ -453,22 +454,53 @@ const fixed2Num = computed(() => {
   return settingStore.getFix2Num
 })
 
-const getConsumerVoucherAmountNumber = (item) => {
-  const ruleValue = settingStore.getCouponRuleValue
+/** 按长护险抹零配置处理后的补贴金额 */
+const formatLongCareAmount = (amount) => {
+  const ruleValue = settingStore.getLongCareRuleValue
+  if (ruleValue == 2 || ruleValue == '2') {
+    return Number(formatRound(amount || 0))
+  }
+  return Number(amount || 0)
+}
+
+/** 消费券/长护险都按各自的抹零配置单独取数 */
+const hasSourceRule = (item) =>
+  item.expenseSource == 'consumer_voucher' || item.expenseSource == 'long_term_care_insurance'
+
+/** 取对应来源的抹零规则:2=取整,其它=保留两位小数 */
+const getSourceRuleValue = (item) =>
+  item.expenseSource == 'consumer_voucher'
+    ? settingStore.getCouponRuleValue
+    : settingStore.getLongCareRuleValue
+
+/** 按来源规则取金额(数字) */
+const getSourceAmountNumber = (item) => {
+  const ruleValue = getSourceRuleValue(item)
   const rawAmount = item?.totalAmount ?? 0
   if (ruleValue == 2 || ruleValue == '2') {
     return Number(formatRound(rawAmount))
   }
-  return Number(formatNum(rawAmount))
+  if(item.expenseSource == 'consumer_voucher'){
+    return Number(formatNum(rawAmount))
+  }
+  if(item.expenseSource == 'long_term_care_insurance'){
+    return Number(rawAmount)
+  }
 }
 
-const getConsumerVoucherAmountText = (item) => {
-  const ruleValue = settingStore.getCouponRuleValue
+/** 按来源规则取金额(展示用字符串) */
+const getSourceAmountText = (item) => {
+  const ruleValue = getSourceRuleValue(item)
   const rawAmount = item?.totalAmount ?? 0
   if (ruleValue == 2 || ruleValue == '2') {
     return formatRound(rawAmount)
   }
-  return formatNum(rawAmount)
+  if(item.expenseSource == 'consumer_voucher'){
+    return formatNum(rawAmount)
+  }
+  if(item.expenseSource == 'long_term_care_insurance'){
+    return Number(rawAmount)
+  }
 }
 
 // 账单总额
@@ -480,53 +512,53 @@ const totalPayAmount = computed(() => {
   dataForm.value.items.map((item) => {
     if (item.itemName != '长护险护理补贴费用') {
       num1 +=
-        item.expenseSource == 'consumer_voucher'
-          ? getConsumerVoucherAmountNumber(item)
+        hasSourceRule(item)
+          ? getSourceAmountNumber(item)
           : Number(item.roundAmount)
       num2 +=
-        item.expenseSource == 'consumer_voucher'
-          ? getConsumerVoucherAmountNumber(item)
+        hasSourceRule(item)
+          ? getSourceAmountNumber(item)
           : Number(item.roundTwoDecimalAmount)
       if (item.isMonthlyExpense == 1 && dataForm.value.type == 1) {
         if (settingStore.getAdmissionBill == 2) {
           // 向上取整
           num3 +=
-            item.expenseSource == 'consumer_voucher'
-              ? getConsumerVoucherAmountNumber(item)
+            hasSourceRule(item)
+              ? getSourceAmountNumber(item)
               : Number(formatCeil(item.totalAmount))
         } else if (settingStore.getAdmissionBill == 3) {
           // 四舍五入保留两位
           num3 +=
-            item.expenseSource == 'consumer_voucher'
-              ? getConsumerVoucherAmountNumber(item)
+            hasSourceRule(item)
+              ? getSourceAmountNumber(item)
               : Number(item.roundTwoDecimalAmount)
         } else if (settingStore.getAdmissionBill == 4) {
           // 四舍五入取整
           num3 +=
-            item.expenseSource == 'consumer_voucher'
-              ? getConsumerVoucherAmountNumber(item)
+            hasSourceRule(item)
+              ? getSourceAmountNumber(item)
               : Number(item.roundAmount)
         } else {
           num3 +=
-            item.expenseSource == 'consumer_voucher'
-              ? getConsumerVoucherAmountNumber(item)
+            hasSourceRule(item)
+              ? getSourceAmountNumber(item)
               : Number(item.totalAmount)
         }
       } else {
         if (rounding.value) {
           num3 +=
-            item.expenseSource == 'consumer_voucher'
-              ? getConsumerVoucherAmountNumber(item)
+            hasSourceRule(item)
+              ? getSourceAmountNumber(item)
               : Number(item.roundAmount)
         } else if (fixed2Num.value) {
           num3 +=
-            item.expenseSource == 'consumer_voucher'
-              ? getConsumerVoucherAmountNumber(item)
+            hasSourceRule(item)
+              ? getSourceAmountNumber(item)
               : Number(item.roundTwoDecimalAmount)
         } else {
           num3 +=
-            item.expenseSource == 'consumer_voucher'
-              ? getConsumerVoucherAmountNumber(item)
+            hasSourceRule(item)
+              ? getSourceAmountNumber(item)
               : Number(item.totalAmount)
         }
       }
@@ -564,8 +596,8 @@ const tableRowPrice = (row) => {
 
 // 表格1总额单项小计
 const tableRowPay = (row) => {
-  if (row.expenseSource == 'consumer_voucher') {
-    return getConsumerVoucherAmountText(row)
+  if (hasSourceRule(row)) {
+    return getSourceAmountText(row)
   }
   if (dataForm.value.type == '1' && row.isMonthlyExpense == 1) {
     if (settingStore.getAdmissionBill == 2) {
@@ -599,43 +631,43 @@ const tableSubtotal = computed(() => {
       if (settingStore.getAdmissionBill == 2) {
         // 向上取整
         num +=
-          item.expenseSource == 'consumer_voucher'
-            ? getConsumerVoucherAmountNumber(item)
+          hasSourceRule(item)
+            ? getSourceAmountNumber(item)
             : Number(formatCeil(item.totalAmount))
       } else if (settingStore.getAdmissionBill == 3) {
         // 四舍五入保留两位
         num +=
-          item.expenseSource == 'consumer_voucher'
-            ? getConsumerVoucherAmountNumber(item)
+          hasSourceRule(item)
+            ? getSourceAmountNumber(item)
             : Number(item.roundTwoDecimalAmount)
       } else if (settingStore.getAdmissionBill == 4) {
         // 四舍五入取整
         num +=
-          item.expenseSource == 'consumer_voucher'
-            ? getConsumerVoucherAmountNumber(item)
+          hasSourceRule(item)
+            ? getSourceAmountNumber(item)
             : Number(item.roundAmount)
       } else {
         // 应收
         num +=
-          item.expenseSource == 'consumer_voucher'
-            ? getConsumerVoucherAmountNumber(item)
+          hasSourceRule(item)
+            ? getSourceAmountNumber(item)
             : Number(item.totalAmount)
       }
     } else {
       if (rounding.value) {
         num +=
-          item.expenseSource == 'consumer_voucher'
-            ? getConsumerVoucherAmountNumber(item)
+          hasSourceRule(item)
+            ? getSourceAmountNumber(item)
             : Number(item.roundAmount)
       } else if (fixed2Num.value) {
         num +=
-          item.expenseSource == 'consumer_voucher'
-            ? getConsumerVoucherAmountNumber(item)
+          hasSourceRule(item)
+            ? getSourceAmountNumber(item)
             : Number(item.roundTwoDecimalAmount)
       } else {
         num +=
-          item.expenseSource == 'consumer_voucher'
-            ? getConsumerVoucherAmountNumber(item)
+          hasSourceRule(item)
+            ? getSourceAmountNumber(item)
             : Number(item.totalAmount)
       }
     }

+ 52 - 15
src/views/elderly/fee/bill-pay/unpay-preview.vue

@@ -171,6 +171,40 @@ const fixed2Num = computed(() => {
   return settingStore.getFix2Num
 })
 
+// 长护险规则
+const longCareRuleValue = computed(() => {
+  return settingStore.getLongCareRuleValue
+})
+
+/** 消费券/长护险都按各自的抹零配置单独取数 */
+const hasSourceRule = (item) =>
+  item.expenseSource == 'consumer_voucher' || item.expenseSource == 'long_term_care_insurance'
+
+/** 长护险金额:按长护险抹零配置取数(2=取整,其它=保留两位小数) */
+const getLongCareAmountNumber = (item) => {
+  const rawAmount = item?.totalAmount ?? 0
+  if (longCareRuleValue.value == 2 || longCareRuleValue.value == '2') {
+    return Number(formatRound(rawAmount))
+  }
+  return Number(rawAmount)
+}
+
+/** 长护险金额:展示用字符串 */
+const getLongCareAmountText = (item) => {
+  if (longCareRuleValue.value == 2 || longCareRuleValue.value == '2') {
+    return formatRound(item?.totalAmount ?? 0)
+  }
+  return Number(item?.totalAmount ?? 0)
+}
+
+/** 按长护险规则处理后的补贴金额 */
+const formatLongCareAmount = (amount) => {
+  if (longCareRuleValue.value == 2 || longCareRuleValue.value == '2') {
+    return Number(formatRound(amount || 0))
+  }
+  return Number(amount || 0)
+}
+
 const tempResValue = ref({})
 
 // 家属信息:后端响应 relatives 数组(每项含 peopleName/relation/phone/idCard/insurance/isBillSender 等)
@@ -178,6 +212,7 @@ const relatives = ref<any[]>([])
 const relativeDialogVisible = ref(false)
 
 const getItemPreviewAmount = (item) => {
+  // 消费券/长护险优先按各自抹零规则取数(与表格展示保持一致)
   if (item.expenseSource == 'consumer_voucher') {
     const ruleValue = settingStore.getCouponRuleValue
     if (ruleValue == 2 || ruleValue == '2') {
@@ -185,6 +220,9 @@ const getItemPreviewAmount = (item) => {
     }
     return Number(formatNum(item.totalAmount))
   }
+  if (item.expenseSource == 'long_term_care_insurance') {
+    return getLongCareAmountNumber(item)
+  }
 
   if (dataForm.value.type == '1' && item.isMonthlyExpense == 1) {
     if (settingStore.getAdmissionBill == 2) {
@@ -196,8 +234,6 @@ const getItemPreviewAmount = (item) => {
     } else {
       return Number(formatNum(item.totalAmount))
     }
-  } else if (item.expenseSource == 'long_term_care_insurance') {
-    return Number(formatNum(item.totalAmount))
   } else if (rounding.value) {
     return Number(item.roundAmount)
   } else if (fixed2Num.value) {
@@ -232,7 +268,7 @@ const open = async (id) => {
   dataForm.value = res
   res.items.map((item) => {
     if (item.expenseSource == 'long_term_care_insurance') {
-      longTermAmount.value += item.actualPrice
+      longTermAmount.value += formatLongCareAmount(item.actualPrice)
       item.payStatus = 0
       flag = true
       // 遍历长护险
@@ -249,9 +285,9 @@ const open = async (id) => {
 
     if (hasPartialPay) {
       total.value +=
-        item.expenseSource == 'consumer_voucher' ? getItemPreviewAmount(item) : item.roundAmount
+        hasSourceRule(item) ? getItemPreviewAmount(item) : item.roundAmount
       totalDecimal.value +=
-        item.expenseSource == 'consumer_voucher'
+        hasSourceRule(item)
           ? getItemPreviewAmount(item)
           : item.roundTwoDecimalAmount
       return
@@ -269,11 +305,9 @@ const open = async (id) => {
       } catch (e) {}
     }
     total.value +=
-      item.expenseSource == 'consumer_voucher' ? getItemPreviewAmount(item) : item.roundAmount
+      hasSourceRule(item) ? getItemPreviewAmount(item) : item.roundAmount
     totalDecimal.value +=
-      item.expenseSource == 'consumer_voucher'
-        ? getItemPreviewAmount(item)
-        : item.roundTwoDecimalAmount
+      hasSourceRule(item) ? getItemPreviewAmount(item) : item.roundTwoDecimalAmount
   })
 
   // 有长护险
@@ -281,16 +315,18 @@ const open = async (id) => {
     if (res.expenseSubsidyDO && res.expenseSubsidyDO.length) {
       let num = 0
       res.expenseSubsidyDO.map((item) => {
-        num += item.amount
+        // 按长护险抹零配置取数
+        const amount = formatLongCareAmount(item.amount)
+        num += amount
 
         dataForm.value.items.push({
           expenseSource: 'long_term_care_insurance',
           itemCategoryName: '长护险',
           itemName: '长护险抵扣',
           description: item.remarks ? item.remarks : item.month + '长护险抵扣',
-          roundAmount: -item.amount,
-          roundTwoDecimalAmount: -item.amount,
-          totalAmount: -item.amount,
+          roundAmount: -amount,
+          roundTwoDecimalAmount: -amount,
+          totalAmount: -amount,
           createdTime: item.month,
           payTime: '',
           payStatus: item.status
@@ -351,12 +387,15 @@ const caclAdminssionTotalPrice = computed(() => {
 
 // 总额
 const calcTotalPrice = (item) => {
+  // 消费券/长护险优先按各自抹零规则取数
   if (item.expenseSource == 'consumer_voucher') {
     const ruleValue = settingStore.getCouponRuleValue
     if (ruleValue == 2 || ruleValue == '2') {
       return formatRound(item.totalAmount)
     }
     return formatNum(item.totalAmount)
+  } else if (item.expenseSource == 'long_term_care_insurance') {
+    return getLongCareAmountText(item)
   } else if (dataForm.value.type == '1' && item.isMonthlyExpense == 1) {
     if (settingStore.getAdmissionBill == 2) {
       // 向上取整
@@ -371,8 +410,6 @@ const calcTotalPrice = (item) => {
       // 应收
       return formatNum(item.totalAmount)
     }
-  } else if (item.expenseSource == 'long_term_care_insurance') {
-    return formatNum(item.totalAmount)
   } else if (rounding.value) {
     return formatRound(item.roundAmount)
   } else if (fixed2Num.value) {

+ 14 - 0
src/views/elderly/fee/comprehensive-setting/index.vue

@@ -383,6 +383,20 @@ const list = ref<any[]>([
         rule: true,
         title: '财务模块配置'
       },
+      {
+        configName: '长护险抹零设置',
+        desc: '在账单缴费时长护险抹零设置',
+        type: 'select',
+        placeholder: '请选择',
+        dict: DICT_TYPE.CONSUMER_ROLE_AMOUNT,
+        value: '',
+        id: '',
+        label: '请选择',
+        prop: 'insurance_amount',
+        tgType: true,
+        rule: true,
+        title: '财务模块配置'
+      },
     ]
   },
   {

+ 6 - 11
src/views/elderly/fee/deposit/Print.vue

@@ -84,8 +84,7 @@ import { getDictLabel, DICT_TYPE } from '@/utils/dict'
 import { getDepositDetail } from '@/api/elderly/fee/deposit'
 import { fmtDict } from '@/utils'
 import { getOverheadListByType } from '@/api/elderly/fee/overheadCharge'
-import { getTenant } from '@/api/system/tenant'
-import { getTenantId } from '@/utils/auth'
+import { getSealUrlByType, SEAL_TYPE } from '@/api/system/commonSeal'
 defineOptions({ name: 'DepositPrint' })
 
 const tenantName = useUserStore().getTenantName // 当前登录的编号
@@ -125,20 +124,16 @@ const pageTitle = ref('')
 const pageType = ref(0)
 const payText = ref('')
 const singleType = ref(true)
-// 当前机构公章(来自租户接口 expenseCachetUrl);为空字符串时签章区不渲染
+// 当前机构押金公章(来自 /system/common-seal/list,type=deposit_seal);为空字符串时签章区不渲染
 const sealUrl = ref('')
 
 /** 打开弹窗 */
 const open = async (elderId, id, type = 1, single = true) => {
   dialogVisible.value = true
-  // 并行拉取机构公章(容错,租户接口失败不影响打印主流程)
-  getTenant(getTenantId() as number)
-    .then((tenant) => {
-      sealUrl.value = tenant?.expenseCachetUrl || ''
-    })
-    .catch(() => {
-      sealUrl.value = ''
-    })
+  // 并行拉取机构押金公章(容错,公章接口失败不影响打印主流程)
+  getSealUrlByType(SEAL_TYPE.DEPOSIT).then((url) => {
+    sealUrl.value = url || ''
+  })
   pageType.value = type
   singleType.value = single
   /** 押金 */

+ 155 - 73
src/views/system/official-seal/index.vue

@@ -1,137 +1,219 @@
 <template>
   <ContentWrap title="机构公章管理">
-    <div v-loading="loading" class="seal-container">
-      <el-alert type="info" :closable="false" show-icon class="mb-20px">
-        当前机构:<strong>{{ tenantName || '—' }}</strong>,公章仅对本机构生效。
-      </el-alert>
-
-      <div class="seal-box">
-        <UploadImg
-          v-model="sealUrl"
-          v-model:fileName="fileName"
-          :width="'220px'"
-          :height="'220px'"
-          :show-delete="false"
-        />
+    <template #header>
+      <div class="seal-header">
+        <el-button :loading="loading" @click="handleRefresh">
+          <Icon icon="ep:refresh" class="mr-5px" />刷新
+        </el-button>
       </div>
+    </template>
+    <el-alert type="info" :closable="false" show-icon class="mb-20px">
+      当前机构:<strong>{{ tenantName || '—' }}</strong
+      >,公章仅对本机构生效,每个类型只能上传一枚公章。
+    </el-alert>
+
+    <div v-loading="loading" class="seal-container">
+      <el-empty
+        v-if="!typeOptions.length"
+        description="未获取到公章类型字典(common_seal),请先配置字典"
+      />
+
+      <div v-for="opt in typeOptions" :key="opt.value" class="seal-card">
+        <div class="seal-card-title">{{ opt.label }}</div>
 
-      <div class="seal-tip">支持 jpg / png / gif,大小不超过 5MB。点击图片即可上传或替换。</div>
+        <div class="seal-box">
+          <UploadImg
+            v-model="formMap[opt.value].url"
+            v-model:fileName="formMap[opt.value].fileName"
+            :width="'220px'"
+            :height="'220px'"
+            :show-delete="false"
+          />
+        </div>
 
-      <div class="seal-actions">
-        <el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
-        <el-button type="danger" plain @click="handleDelete">删除公章</el-button>
+        <div class="seal-tip">支持 jpg / png / gif,大小不超过 5MB。点击图片即可上传或替换。</div>
+
+        <div class="seal-actions">
+          <el-button
+            type="primary"
+            :loading="savingType === opt.value"
+            @click="handleSave(opt.value)"
+          >
+            保存
+          </el-button>
+          <el-button type="danger" plain :disabled="!formMap[opt.value].id" @click="handleDelete(opt.value)">
+            删除公章
+          </el-button>
+        </div>
       </div>
     </div>
   </ContentWrap>
 </template>
 
 <script setup lang="ts">
-import { ref, computed, onMounted } from 'vue'
+import { ref, reactive, computed, onMounted } from 'vue'
 import { ElMessageBox } from 'element-plus'
 import { UploadImg } from '@/components/UploadFile'
 import { getTenantId } from '@/utils/auth'
 import { useUserStore } from '@/store/modules/user'
+import { useDictStore } from '@/store/modules/dict'
 import { useMessage } from '@/hooks/web/useMessage'
-import { getTenant, updateTenant, type TenantVO } from '@/api/system/tenant'
+import { getStrDictOptions, DICT_TYPE, type StringDictDataType } from '@/utils/dict'
+import {
+  getCommonSealList,
+  createCommonSeal,
+  updateCommonSeal,
+  deleteCommonSeal,
+  type CommonSealVO
+} from '@/api/system/commonSeal'
+
+defineOptions({ name: 'OfficialSeal' })
+
+/** 单个类型公章的编辑态 */
+interface SealForm {
+  id?: number
+  url: string
+  fileName: string
+}
 
 const message = useMessage()
 const userStore = useUserStore()
-const tenantId = getTenantId()
+const dictStore = useDictStore()
+const tenantId = getTenantId() as number
 const tenantName = computed(() => userStore.tenant?.name || '')
 
 const loading = ref(false)
-const saving = ref(false)
-const sealUrl = ref('')
-const fileName = ref('')
-
-// 当前租户全量信息(保存 / 删除时要带回全部字段,避免被后端清空)
-const currentTenant = ref<TenantVO | null>(null)
-
-// 演示用占位公章:用于租户尚未上传时的占位预览
-const DEMO_SEAL =
-  'data:image/svg+xml;utf8,' +
-  encodeURIComponent(
-    '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"><circle cx="100" cy="100" r="92" fill="none" stroke="#c0392b" stroke-width="6"/><circle cx="100" cy="100" r="76" fill="none" stroke="#c0392b" stroke-width="2"/><text x="100" y="108" font-size="22" fill="#c0392b" text-anchor="middle" font-family="serif">公章示例</text></svg>'
-  )
-
-/** 从租户接口读取当前机构公章(字段 expenseCachetUrl) */
-const loadSeal = async () => {
+const savingType = ref('')
+
+/** 公章类型(字典 common_seal) */
+const typeOptions = ref<StringDictDataType[]>([])
+/** 各类型公章编辑数据,key 为字典值 */
+const formMap = reactive<Record<string, SealForm>>({})
+
+/** 按字典构建表单结构,保证模板中 formMap[type] 一定存在 */
+const buildForms = () => {
+  typeOptions.value = getStrDictOptions(DICT_TYPE.COMMON_SEAL)
+  typeOptions.value.forEach((opt) => {
+    if (!formMap[opt.value]) {
+      formMap[opt.value] = { id: undefined, url: '', fileName: '' }
+    }
+  })
+}
+
+/** 拉取当前机构公章列表,按类型回填 */
+const loadSealList = async () => {
   loading.value = true
   try {
-    const data: any = await getTenant(tenantId as number)
-    currentTenant.value = data
-    sealUrl.value = data?.expenseCachetUrl || ''
-    fileName.value = ''
+    const list: CommonSealVO[] = (await getCommonSealList({ tenantId })) || []
+    Object.keys(formMap).forEach((key) => {
+      formMap[key].id = undefined
+      formMap[key].url = ''
+      formMap[key].fileName = ''
+    })
+    list.forEach((item) => {
+      const key = item?.type || ''
+      if (!key) return
+      if (!formMap[key]) {
+        formMap[key] = { id: undefined, url: '', fileName: '' }
+      }
+      formMap[key].id = item.id
+      formMap[key].url = item.url || ''
+    })
   } catch {
-    sealUrl.value = DEMO_SEAL
-    message.notifyWarning('租户接口读取失败,当前显示演示数据')
+    message.error('公章列表加载失败,请稍后重试')
   } finally {
     loading.value = false
   }
 }
 
-const handleSave = async () => {
-  if (!sealUrl.value || sealUrl.value === DEMO_SEAL) {
+/** 手动刷新:重新拉取列表,避免新增 / 更新后数据延迟 */
+const handleRefresh = async () => {
+  await loadSealList()
+  message.success('已刷新')
+}
+
+/** 保存:已存在则更新,否则创建 */
+const handleSave = async (type: string) => {
+  const form = formMap[type]
+  if (!form?.url) {
     message.error('请先上传公章图片')
     return
   }
-  if (!currentTenant.value) {
-    message.error('租户信息未加载,请刷新页面后重试')
-    return
-  }
-  saving.value = true
+  savingType.value = type
   try {
-    // 全量带回其他字段,只覆盖 expenseCachetUrl
-    await updateTenant({
-      ...currentTenant.value,
-      expenseCachetUrl: sealUrl.value
-    })
+    if (form.id) {
+      await updateCommonSeal({ id: form.id, type, url: form.url, tenantId })
+    } else {
+      await createCommonSeal({ type, url: form.url, tenantId })
+    }
     message.success('公章已保存')
+    await loadSealList()
   } catch {
-    message.error('保存失败,请稍后重试')
+    // 失败提示由请求拦截器统一处理
   } finally {
-    saving.value = false
+    savingType.value = ''
   }
 }
 
-const handleDelete = async () => {
-  if (!sealUrl.value || sealUrl.value === DEMO_SEAL) {
+/** 删除公章 */
+const handleDelete = async (type: string) => {
+  const form = formMap[type]
+  if (!form?.id) {
     message.warning('当前没有可删除的公章')
     return
   }
-  if (!currentTenant.value) {
-    message.error('租户信息未加载,请刷新页面后重试')
-    return
-  }
   try {
-    await ElMessageBox.confirm('确认删除当前机构公章?删除后需重新上传。', '提示', {
+    await ElMessageBox.confirm('确认删除该类型公章?删除后需重新上传。', '提示', {
       type: 'warning'
     })
   } catch {
     return
   }
   try {
-    // 后端无 delete 接口:"删除"= update 时把字段清空
-    await updateTenant({
-      ...currentTenant.value,
-      expenseCachetUrl: ''
-    })
-    sealUrl.value = ''
-    fileName.value = ''
+    await deleteCommonSeal(form.id)
+    form.id = undefined
+    form.url = ''
+    form.fileName = ''
     message.success('公章已删除')
   } catch {
-    message.error('删除失败,请稍后重试')
+    // 失败提示由请求拦截器统一处理
   }
 }
 
-onMounted(loadSeal)
+onMounted(async () => {
+  // 字典可能尚未加载(或新增了 common_seal),兜底重新拉取一次
+  if (!getStrDictOptions(DICT_TYPE.COMMON_SEAL).length) {
+    await dictStore.resetDict()
+  }
+  buildForms()
+  await loadSealList()
+})
 </script>
 
 <style scoped lang="scss">
+.seal-header {
+  display: flex;
+  justify-content: flex-end;
+  flex: 1;
+}
 .seal-container {
-  max-width: 480px;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 24px;
   padding: 10px 0;
 }
+.seal-card {
+  width: 280px;
+  padding: 16px;
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
+}
+.seal-card-title {
+  margin-bottom: 12px;
+  font-size: 15px;
+  font-weight: 600;
+  text-align: center;
+}
 .seal-box {
   display: flex;
   justify-content: center;