documentaryRecord.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. <template>
  2. <PageMain class="vh100">
  3. <el-form v-model="form" label-width="80">
  4. <el-row :gutter="20">
  5. <el-col :span="8">
  6. <el-form-item prop="recordId" label="记录编号">
  7. <el-input v-model="form.recordId"></el-input>
  8. </el-form-item>
  9. </el-col>
  10. <el-col :span="8">
  11. <el-form-item prop="companyId" label="保险公司">
  12. <el-select v-model="form.companyId" placeholder="请选择" clearable filterable>
  13. <el-option v-for="item in companyList" :key="item.id" :value="item.id" :label="item.name"></el-option>
  14. </el-select>
  15. </el-form-item>
  16. </el-col>
  17. <el-col :span="8">
  18. <el-form-item prop="invoiceParty" label="开票方">
  19. <el-select v-model="form.invoiceParty" clearable>
  20. <el-option v-for="item in optionObj['invoice_party']" :key="item.value" :value="item.value"
  21. :label="item.label"></el-option>
  22. </el-select>
  23. </el-form-item>
  24. </el-col>
  25. <el-col :span="8">
  26. <el-form-item prop="invoiceType" label="应收项">
  27. <el-select v-model="form.accountsReceivable" clearable>
  28. <el-option v-for="item in optionObj['invoice_risk_type']" :key="item.value" :value="item.value"
  29. :label="item.label"></el-option>
  30. </el-select>
  31. </el-form-item>
  32. </el-col>
  33. <el-col :span="8">
  34. <el-form-item prop="status" label="结算状态">
  35. <el-select v-model="form.settlementStatus" clearable>
  36. <el-option v-for="item in optionObj['settlement_status']" :key="item.value" :value="item.value"
  37. :label="item.label"></el-option>
  38. </el-select>
  39. </el-form-item>
  40. </el-col>
  41. <el-col :span="8">
  42. <el-form-item prop="invoicePerson" label="开票人">
  43. <el-input v-model="form.invoicePerson"></el-input>
  44. </el-form-item>
  45. </el-col>
  46. <el-col :span="8">
  47. <el-form-item prop="invoiceDate" label="开票时间">
  48. <el-date-picker type="daterange" v-model="form.invoiceDate" value-format="YYYY-MM-DD HH:mm:ss"
  49. format="YYYY-MM-DD HH:mm:ss" />
  50. </el-form-item>
  51. </el-col>
  52. <el-col :span="16" style="display: flex; align-items: start; justify-content: flex-end">
  53. <el-button type="primary" @click="search">查询</el-button>
  54. <el-button plain type="primary" @click="reset">重置</el-button>
  55. </el-col>
  56. </el-row>
  57. </el-form>
  58. <el-table :data="tableData" style="height: calc(100% - 192px)" show-summary :summary-method="getSummaries">
  59. <el-table-column prop="id" label="开票记录编号" width="200"></el-table-column>
  60. <el-table-column prop="companyAllName" label="保险公司" width="150">
  61. </el-table-column>
  62. <el-table-column prop="companyNameSimple" label="保险机构" width="150"></el-table-column>
  63. <el-table-column prop="invoiceNo" label="发票号" width="150"></el-table-column>
  64. <el-table-column prop="invoiceParty" label="开票方" width="150"></el-table-column>
  65. <el-table-column prop="remainSettlementAmount" label="未结算金额" width="100"></el-table-column>
  66. <el-table-column prop="accountsReceivable" label="应收项" width="100"></el-table-column>
  67. <el-table-column prop="receivableAmount" label="应收金额" width="120"></el-table-column>
  68. <el-table-column prop="paymentReason" label="差额原因" width="120"></el-table-column>
  69. <el-table-column prop="taxPoint" label="税点" width="120"></el-table-column>
  70. <el-table-column prop="receivableSupervisePremium" label="开票金额" width="120"></el-table-column>
  71. <el-table-column prop="settlementStatus" label="结算状态" width="120">
  72. </el-table-column>
  73. <el-table-column prop="settlementAmount" label="已结算金额" width="120"></el-table-column>
  74. <el-table-column prop="createBy" label="开票人" width="120"></el-table-column>
  75. <el-table-column prop="createTime" label="开票时间" width="150"></el-table-column>
  76. <el-table-column label="操作" width="300" fixed="right" align="center">
  77. <template #default="scope">
  78. <div class="flex a-c ">
  79. <el-button link type="primary" :disabled="scope.row.settlementStatus == '已结算'"
  80. @click="handleCount(scope.row)">结算</el-button>
  81. <el-button link type="primary" :disabled="scope.row.settlementStatus == '已结算'"
  82. @click="handleDelete(scope.row)">删除</el-button>
  83. <el-button link type="primary" :disabled="scope.row.settlementStatus == '已结算'"
  84. @click="handleSettlement(scope.row)">标识结清</el-button>
  85. <el-dropdown placement="bottom-start" trigger="click">
  86. <span class="el-dropdown-link">
  87. 明细查询
  88. </span>
  89. <template #dropdown>
  90. <el-dropdown-menu>
  91. <el-dropdown-item :disabled="scope.row.settlementStatus == '未结算'"
  92. @click="handleCountRecord(scope.row)">结算记录</el-dropdown-item>
  93. <el-dropdown-item :disabled="scope.row.settlementStatus == '未结算'"
  94. @click="handleInvoiceDetail(scope.row)">开票明细</el-dropdown-item>
  95. </el-dropdown-menu>
  96. </template>
  97. </el-dropdown>
  98. </div>
  99. </template>
  100. </el-table-column>
  101. </el-table>
  102. <div class="pagination">
  103. <el-pagination v-model:current-page="pages" v-model:page-size="size" :page-sizes="[10, 20, 30, 40]"
  104. layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange"
  105. @current-change="handleCurrentChange"></el-pagination>
  106. </div>
  107. <el-dialog :close-on-click-modal="false" v-model="countShow" width="600" title="结算">
  108. <el-form ref="CountForm" :model="countForm" :rules="countRules" label-width="120">
  109. <el-form-item prop="invoiceParty" label="开票方">
  110. <el-input v-model="countForm.invoiceParty" disabled placeholder="请输入"></el-input>
  111. </el-form-item>
  112. <el-form-item prop="receivableAmount" label="应收金额">
  113. <el-input v-model="countForm.receivableAmount" disabled placeholder="请输入"></el-input>
  114. </el-form-item>
  115. <!-- <el-form-item prop="overinflatedAmount" label="虚增金额">
  116. <el-input v-model="countForm.overinflatedAmount" disabled placeholder="请输入"></el-input>
  117. </el-form-item> -->
  118. <el-form-item prop="remainSettlementAmount" label="未结算金额">
  119. <el-input v-model="countForm.remainSettlementAmount" disabled placeholder="请输入"></el-input>
  120. </el-form-item>
  121. <el-form-item prop="taxPoint" label="税点">
  122. <el-input v-model="countForm.taxPoint" disabled placeholder="请输入"></el-input>
  123. </el-form-item>
  124. <el-form-item prop="receivableSupervisePremium" label="开票金额">
  125. <el-input v-model="countForm.receivableSupervisePremium" disabled placeholder="请输入"></el-input>
  126. </el-form-item>
  127. <el-form-item prop="actualReceivedAmount" label="实收金额">
  128. <el-input-number v-model="countForm.actualReceivedAmount" style="width: 100%;" :min="0"
  129. :max="countForm.remainSettlementAmount" :precision="2" controls-position="right" @change="handleChange()" />
  130. </el-form-item>
  131. <el-form-item label="回款差额">
  132. <el-input v-model="countForm.settlementPaymentDifference" disabled placeholder="自动计算" />
  133. </el-form-item>
  134. <el-form-item prop="settlementPaymentReason" label="差额原因">
  135. <el-input v-model="countForm.settlementPaymentReason" placeholder="请输入"></el-input>
  136. </el-form-item>
  137. <el-form-item prop="receivePaymentDate" label="收款日期">
  138. <el-date-picker v-model="countForm.receivePaymentDate" type="date" value-format="YYYY-MM-DD" />
  139. </el-form-item>
  140. <el-form-item prop="attachmentIds" label="收款凭证" class="upload-btn">
  141. <el-upload style="margin: 0 10px 10px 0" :auto-upload="false" multiple v-if="!countForm.h"
  142. :show-file-list="false" accept=".png, .jpg, .jpeg" @change="uploadImage" drag>
  143. <el-button size="large" icon="Plus" />
  144. </el-upload>
  145. <div class="imageDiv" v-for="(item, index) in imageUrl" :key="item">
  146. <img :src="item" style="width: 80px; height: 80px;" />
  147. <div class="model">
  148. <span class="icon-view" @click="handelViewImage(item, index)">
  149. <el-icon>
  150. <View />
  151. </el-icon>
  152. </span>
  153. <span class="icon-del" @click="handelDelImage(item, index)">
  154. <el-icon>
  155. <Delete />
  156. </el-icon>
  157. </span>
  158. </div>
  159. </div>
  160. </el-form-item>
  161. </el-form>
  162. <template #footer>
  163. <el-button class="border-[#0083FF] color-[#0083FF]" plain @click="handelCancel">取消</el-button>
  164. <el-button type="primary" @click="handelComfirm(CountForm)">确定</el-button>
  165. </template>
  166. </el-dialog>
  167. </PageMain>
  168. <el-image-viewer v-if="imageShow" :url-list="[bigImageUrl]" @close="imageShow = false" />
  169. </template>
  170. <script setup lang="ts">
  171. import api from "@/api";
  172. import { useRoute, useRouter } from 'vue-router'
  173. import { ElMessage, ElMessageBox } from 'element-plus'
  174. const route = useRoute()
  175. const router = useRouter()
  176. let form = ref({})
  177. let tableData = ref([])
  178. let pages = ref(1)
  179. let size = ref(10)
  180. let total = ref(0)
  181. const handleSizeChange = (size) => {
  182. size.value = size
  183. initList()
  184. }
  185. const handleCurrentChange = (page) => {
  186. pages.value = page
  187. initList()
  188. }
  189. const search = () => {
  190. pages.value = 1
  191. size.value = 10
  192. initList()
  193. }
  194. const reset = () => {
  195. form.value = {
  196. recordId: '',
  197. companyId: '',
  198. invoiceParty: '',
  199. accountsReceivable: '',
  200. settlementStatus: '',
  201. invoicePerson: '',
  202. invoiceDate: []
  203. }
  204. pages.value = 1
  205. size.value = 10
  206. initList()
  207. }
  208. const invoiceId = route.query.invoiceId
  209. if (invoiceId) {
  210. form.value.recordId = invoiceId
  211. }
  212. let countShow = ref(false)
  213. let countForm = ref({})
  214. let countRules = ref({
  215. actualReceivedAmount: [{ required: true, trigger: "blur", message: "请输入" }],
  216. receivePaymentDate: [{ required: true, trigger: "change", message: "请选择" }],
  217. attachmentIds: [{ required: true, message: "请上传" }]
  218. })
  219. let imageUrl = ref([])
  220. let attachmentIds = ref([])
  221. let invoicingId = ref('')
  222. const handleCount = (row) => {
  223. countShow.value = true
  224. imageUrl.value = []
  225. attachmentIds.value = []
  226. invoicingId.value = row.id
  227. countForm.value = {
  228. invoiceParty: row.invoiceParty,
  229. remainSettlementAmount: row.remainSettlementAmount,
  230. receivableAmount: row.receivableAmount,
  231. receivableSupervisePremium: row.receivableSupervisePremium,
  232. overinflatedAmount: row.overinflatedAmount,
  233. taxPoint: row.taxPoint,
  234. }
  235. }
  236. const uploadImage = async (file) => {
  237. let files = file.raw
  238. const params = new FormData(); // 声明formData数据类型
  239. params.append("file", files);
  240. params.append("type", "image");
  241. const { code, data, msg } = await api.commonApi.fileUpload(params)
  242. if (code == 200) {
  243. imageUrl.value.push(data.downloadUrl)
  244. attachmentIds.value.push(data.id)
  245. }
  246. }
  247. /** 计算实收金额与应收监管保费的差值,并将结果赋值给付款差额字段 */
  248. const handleChange = async () => {
  249. const num = await calSub(countForm.value.actualReceivedAmount, countForm.value.receivableSupervisePremium)
  250. countForm.value.settlementPaymentDifference = num
  251. }
  252. /** 计算应收监管保费差额并更新虚增金额 */
  253. /**
  254. * 计算两个数值的差值(被减数 - 减数)
  255. * @param minuend 被减数
  256. * @param subtrahend 减数
  257. * @returns 差值,若接口无数据则返回 0
  258. */
  259. const calSub = async (minuend: number = 0, subtrahend: number) => {
  260. const res = await api.financeApi.calSub({ minuend, subtrahend })
  261. return res.data || 0
  262. }
  263. // 标识结清
  264. const handleSettlement = (row: any) => {
  265. ElMessageBox.confirm(
  266. '确定结清吗',
  267. '结清',
  268. {
  269. confirmButtonText: '确定',
  270. type: 'warning',
  271. center: true,
  272. confirmButtonClass: 'el-button--primary',
  273. }
  274. )
  275. .then(() => {
  276. api.financeApi.updateSettlementStatus(row.id).then((res: any) => {
  277. if (res.code == 200) {
  278. ElMessage({
  279. message: res.msg,
  280. type: 'success'
  281. })
  282. initList()
  283. } else {
  284. }
  285. })
  286. })
  287. .catch(() => {
  288. })
  289. }
  290. // 删除
  291. const handleDelete = (row) => {
  292. ElMessageBox.confirm(
  293. '确定要删除吗',
  294. '删除发票',
  295. {
  296. confirmButtonText: '确定',
  297. type: 'error',
  298. center: true,
  299. confirmButtonClass: 'el-button--danger',
  300. }
  301. )
  302. .then(() => {
  303. api.financeApi.deleteFollowInvoiceById(row.id).then((res: any) => {
  304. if (res.code == 200) {
  305. reset()
  306. ElMessage({
  307. message: res.msg,
  308. type: 'success'
  309. })
  310. } else {
  311. }
  312. })
  313. })
  314. .catch(() => {
  315. })
  316. }
  317. // 结算记录查询
  318. const handleCountRecord = (row) => {
  319. router.push({
  320. path: '/receivable/documentary/documentaryReport',
  321. query: {
  322. activeName: '2',
  323. id: row.id
  324. }
  325. })
  326. }
  327. // 开票明细查询
  328. const handleInvoiceDetail = (row) => {
  329. router.push({
  330. path: '/receivable/documentary/documentaryReport',
  331. query: {
  332. activeName: '3',
  333. id: row.id
  334. }
  335. })
  336. }
  337. // 列表
  338. const initList = () => {
  339. api.invoiceRecordApi.invoiceRecord({
  340. pages: pages.value,
  341. size: size.value,
  342. invoiceType: '2',
  343. ...form.value
  344. }).then((res: any) => {
  345. if (res.code == 200) {
  346. tableData.value = res.data.records
  347. total.value = res.data.total
  348. }
  349. })
  350. }
  351. // 保险公司
  352. let companyList = ref([])
  353. const initCompany = () => {
  354. api.insuranceApi.leftList('').then((res: any) => {
  355. if (res.code == 200) {
  356. companyList.value = res.data
  357. }
  358. })
  359. }
  360. let imageShow = ref(false)
  361. let bigImageUrl = ref('')
  362. const handelViewImage = (item, index) => {
  363. bigImageUrl.value = item
  364. imageShow.value = true
  365. }
  366. const handelDelImage = (item, index) => {
  367. imageUrl.value.splice(index, 1)
  368. attachmentIds.value.splice(index, 1)
  369. }
  370. let CountForm = ref()
  371. const handelComfirm = (CountForm) => {
  372. countForm.value.attachmentIds = attachmentIds.value
  373. if (!CountForm) return
  374. CountForm.validate((valid) => {
  375. if (valid) {
  376. api.financeApi.followSettlement({
  377. invoicingId: invoicingId.value,
  378. actualReceivedAmount: countForm.value.actualReceivedAmount,
  379. receivePaymentDate: countForm.value.receivePaymentDate,
  380. attachmentIds: countForm.value.attachmentIds
  381. }).then((res: any) => {
  382. countShow.value = false
  383. if (res.code == 200) {
  384. reset()
  385. ElMessage({
  386. message: res.msg,
  387. type: 'success'
  388. })
  389. }
  390. })
  391. }
  392. })
  393. }
  394. const handelCancel = () => {
  395. countShow.value = false
  396. countForm.value = {
  397. invoiceParty: '',
  398. receivableSupervisePremium: '',
  399. overinflatedAmount: '',
  400. taxPoint: '',
  401. actualReceivedAmount: '',
  402. receivePaymentDate: [],
  403. attachmentIds: []
  404. }
  405. imageUrl.value = []
  406. }
  407. const optionObj = ref({})
  408. const initOption = () => {
  409. let list = ['invoice_risk_type', 'invoice_party', 'settlement_status']
  410. list.forEach(item => {
  411. api.additionalApi.dictDetails(item).then((res: any) => {
  412. if (res.code == 200) {
  413. if (res.data) {
  414. optionObj.value[item] = res.data
  415. }
  416. }
  417. })
  418. })
  419. }
  420. const getSummaries = (param) => {
  421. const { columns, data } = param
  422. const sums: (string | VNode)[] = []
  423. // 指定需要计算的字段列表(可根据需求添加字段名)
  424. const specifiedFields = ['taxPoint', 'actualReceivedAmount'] // 替换为您需要的字段名
  425. columns.forEach((column, index) => {
  426. if (index === 0) {
  427. sums[index] = h('div', { style: { textDecoration: 'underline' } }, [
  428. '',
  429. ])
  430. return
  431. }
  432. // 计算数字类型字段和指定字段
  433. const values = data.map((item) => {
  434. const val = item[column.property]
  435. // 排除空值
  436. if (val === '' || val === null || val === undefined) {
  437. return NaN
  438. }
  439. // 如果是数字类型,直接参与计算
  440. if (typeof val === 'number') {
  441. return val
  442. }
  443. // 如果字段在指定列表中,尝试转换为数字
  444. if (specifiedFields.includes(column.property)) {
  445. const num = Number(val)
  446. return Number.isNaN(num) ? NaN : num
  447. }
  448. // 其他情况不参与计算
  449. return NaN
  450. })
  451. if (!values.every((value) => Number.isNaN(value))) {
  452. const total = values.reduce((prev, curr) => {
  453. if (!Number.isNaN(curr)) {
  454. return prev + curr
  455. } else {
  456. return prev
  457. }
  458. }, 0)
  459. // 保留五位小数
  460. sums[index] = ` ${total.toFixed(5)}`
  461. } else {
  462. sums[index] = '-'
  463. }
  464. })
  465. return sums
  466. }
  467. onMounted(() => {
  468. initList();
  469. initOption();
  470. initCompany();
  471. });
  472. </script>
  473. <style scoped lang="scss">
  474. .pagination {
  475. margin-top: 10px;
  476. display: flex;
  477. justify-content: flex-end;
  478. }
  479. .upload-btn {
  480. :deep(.el-button) {
  481. width: 80px;
  482. height: 80px;
  483. font-size: 30px;
  484. color: #ccc;
  485. }
  486. }
  487. :deep(.el-dropdown-link) {
  488. display: flex;
  489. align-items: center;
  490. margin-left: 15px;
  491. color: var(--el-color-primary);
  492. cursor: pointer;
  493. }
  494. .imageDiv {
  495. position: relative;
  496. width: 80px;
  497. height: 80px;
  498. margin: 0 10px 10px 0;
  499. &:hover {
  500. .model {
  501. display: flex;
  502. align-items: center;
  503. justify-content: center;
  504. background-color: rgba(0, 0, 0, .3);
  505. }
  506. }
  507. img {
  508. display: block;
  509. width: 100%;
  510. height: 100%;
  511. border-radius: 4px;
  512. }
  513. .model {
  514. position: absolute;
  515. width: 80px;
  516. height: 80px;
  517. border-radius: 4px;
  518. display: none;
  519. top: 0;
  520. left: 0;
  521. .icon-view,
  522. .icon-del {
  523. color: #FFFFFF;
  524. margin: 0 10px;
  525. cursor: pointer;
  526. }
  527. }
  528. }
  529. :deep(.el-upload-dragger) {
  530. padding: unset;
  531. border: unset;
  532. border-radius: unset;
  533. }
  534. </style>