|
|
@@ -18,6 +18,26 @@
|
|
|
<h3>报销信息</h3>
|
|
|
<span class="form-badge">{{ editingId ? '编辑' : '新建' }}</span>
|
|
|
</div>
|
|
|
+ <div class="field">
|
|
|
+ <label class="field-label">申请人</label>
|
|
|
+ <div class="picker-trigger" @click="showApplicantPicker = true">
|
|
|
+ <div class="left">
|
|
|
+ <div class="value" :class="{ ph: !applicantLabel }">
|
|
|
+ {{ applicantLabel || '请选择申请人' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <van-icon name="arrow" class="arrow" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label class="field-label">报销日期<span class="req">*</span></label>
|
|
|
+ <input
|
|
|
+ class="form-input"
|
|
|
+ type="date"
|
|
|
+ v-model="form.reimburseDate"
|
|
|
+ @change="refreshTitle"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
<div class="field">
|
|
|
<label class="field-label">报销标题</label>
|
|
|
<input
|
|
|
@@ -29,6 +49,15 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ <van-popup v-model:show="showApplicantPicker" position="bottom" round :style="{ height: '60%' }">
|
|
|
+ <van-picker
|
|
|
+ title="选择申请人"
|
|
|
+ :columns="applicantPickerColumns"
|
|
|
+ @confirm="onApplicantConfirm"
|
|
|
+ @cancel="showApplicantPicker = false"
|
|
|
+ />
|
|
|
+ </van-popup>
|
|
|
+
|
|
|
<!-- 报销明细 -->
|
|
|
<div class="form-card">
|
|
|
<div class="form-header">
|
|
|
@@ -248,11 +277,21 @@ import {
|
|
|
formatExpenseName,
|
|
|
type ExpenseItem,
|
|
|
} from '@/hooks/useExpenseItemTree'
|
|
|
+import { useApplicantOptions } from '@/hooks/useApplicantOptions'
|
|
|
+import { useAuthStore } from '@/stores/auth'
|
|
|
import { SUBMIT_APPROVE_BUTTON_COLOR } from '@/constants/dialog'
|
|
|
import BottomActionBar from '@/components/mobile/BottomActionBar.vue'
|
|
|
|
|
|
const router = useRouter()
|
|
|
const route = useRoute()
|
|
|
+const auth = useAuthStore()
|
|
|
+
|
|
|
+function todayYmd() {
|
|
|
+ const d = new Date()
|
|
|
+ const m = String(d.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(d.getDate()).padStart(2, '0')
|
|
|
+ return `${d.getFullYear()}-${m}-${day}`
|
|
|
+}
|
|
|
|
|
|
interface DetailLine {
|
|
|
expenseItemId: number | string
|
|
|
@@ -263,6 +302,8 @@ interface DetailLine {
|
|
|
const form = reactive({
|
|
|
title: '',
|
|
|
remark: '',
|
|
|
+ applicantId: '' as string,
|
|
|
+ reimburseDate: todayYmd(),
|
|
|
details: [{ expenseItemId: '' as string | number, amount: '', remark: '' }] as DetailLine[],
|
|
|
})
|
|
|
|
|
|
@@ -271,8 +312,44 @@ const editingId = ref<string | null>(null)
|
|
|
const showExpensePicker = ref(false)
|
|
|
const pickingLineIndex = ref(0)
|
|
|
const selectedLeftId = ref<string | null>(null)
|
|
|
+const showApplicantPicker = ref(false)
|
|
|
|
|
|
const { expenseTree, flatList, load: loadExpenseItems } = useExpenseItemTree()
|
|
|
+const { applicantOptions, loadApplicantOptions } = useApplicantOptions()
|
|
|
+
|
|
|
+const applicantLabel = computed(() => {
|
|
|
+ const opt = applicantOptions.value.find((o) => o.value === form.applicantId)
|
|
|
+ return opt?.label || ''
|
|
|
+})
|
|
|
+
|
|
|
+const applicantPickerColumns = computed(() =>
|
|
|
+ applicantOptions.value.map((o) => ({ text: o.label, value: o.value })),
|
|
|
+)
|
|
|
+
|
|
|
+function onApplicantConfirm({ selectedOptions }: any) {
|
|
|
+ const opt = selectedOptions?.[0]
|
|
|
+ if (opt?.value) {
|
|
|
+ form.applicantId = String(opt.value)
|
|
|
+ refreshTitle()
|
|
|
+ refreshWallet()
|
|
|
+ }
|
|
|
+ showApplicantPicker.value = false
|
|
|
+}
|
|
|
+
|
|
|
+async function refreshTitle() {
|
|
|
+ if (!form.reimburseDate) return
|
|
|
+ try {
|
|
|
+ const res: any = await reimburseApi.generateTitle(
|
|
|
+ form.reimburseDate,
|
|
|
+ editingId.value || undefined,
|
|
|
+ form.applicantId || undefined,
|
|
|
+ )
|
|
|
+ const t = res?.data
|
|
|
+ form.title = typeof t === 'string' ? t : t?.title || form.title
|
|
|
+ } catch {
|
|
|
+ /* keep */
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
const walletBalance = ref(0)
|
|
|
const walletInvoices = ref<any[]>([])
|
|
|
@@ -308,9 +385,10 @@ function money(v: unknown) {
|
|
|
|
|
|
async function refreshWallet() {
|
|
|
try {
|
|
|
+ const owner = form.applicantId || undefined
|
|
|
const [sumRes, invRes]: any[] = await Promise.all([
|
|
|
- invoiceWalletApi.mineSummary(),
|
|
|
- invoiceApi.list({ page: 1, pageSize: 200 }),
|
|
|
+ invoiceWalletApi.mineSummary(owner ? { userId: owner } : undefined),
|
|
|
+ invoiceApi.list({ page: 1, pageSize: 200, userId: owner }),
|
|
|
])
|
|
|
walletBalance.value = Number(sumRes?.data?.balance ?? 0)
|
|
|
const d = invRes?.data
|
|
|
@@ -485,6 +563,8 @@ async function onSubmit() {
|
|
|
const payload = {
|
|
|
title: form.title || undefined,
|
|
|
remark: form.remark || undefined,
|
|
|
+ applicantId: form.applicantId || undefined,
|
|
|
+ reimburseDate: form.reimburseDate || undefined,
|
|
|
details: validDetails.map((d) => ({
|
|
|
expenseItemId: String(d.expenseItemId),
|
|
|
amount: Number(d.amount),
|
|
|
@@ -563,6 +643,8 @@ async function loadEditData() {
|
|
|
const data = res.data || res
|
|
|
form.title = data.title || ''
|
|
|
form.remark = data.remark || ''
|
|
|
+ form.applicantId = String(data.applicantId || auth.user?.id || '')
|
|
|
+ form.reimburseDate = data.reimburseDate || todayYmd()
|
|
|
form.details = (data.details || []).map((d: any) => ({
|
|
|
expenseItemId: d.expenseId ?? d.expenseItemId ?? '',
|
|
|
amount: String(d.amount ?? ''),
|
|
|
@@ -573,9 +655,7 @@ async function loadEditData() {
|
|
|
}
|
|
|
const linked = data.invoices || []
|
|
|
selectedInvoiceIds.value = linked.map((x: any) => String(x.id || x.invoiceId)).filter(Boolean)
|
|
|
- if (data.status && data.status !== 'draft' && data.status !== 'rejected') {
|
|
|
- showToast('旧草稿若仍含本地发票附件,请改走发票夹关联')
|
|
|
- }
|
|
|
+ await refreshWallet()
|
|
|
} catch (e: any) {
|
|
|
showToast(e?.message || '加载数据失败')
|
|
|
}
|
|
|
@@ -596,17 +676,13 @@ watch(
|
|
|
|
|
|
onMounted(async () => {
|
|
|
sessionStorage.removeItem('bx_edit_resubmit')
|
|
|
- await Promise.all([loadExpenseItems(), refreshWallet()])
|
|
|
+ if (!form.applicantId) form.applicantId = String(auth.user?.id || '')
|
|
|
+ await Promise.all([loadExpenseItems(), loadApplicantOptions(), refreshWallet()])
|
|
|
await loadEditData()
|
|
|
- if (!editingId.value && !form.title) {
|
|
|
- try {
|
|
|
- const res: any = await reimburseApi.generateTitle()
|
|
|
- if (res.data) {
|
|
|
- form.title = typeof res.data === 'string' ? res.data : res.data.title || ''
|
|
|
- }
|
|
|
- } catch {
|
|
|
- /* skip */
|
|
|
- }
|
|
|
+ if (!editingId.value) {
|
|
|
+ if (!form.applicantId) form.applicantId = String(auth.user?.id || '')
|
|
|
+ if (!form.reimburseDate) form.reimburseDate = todayYmd()
|
|
|
+ await refreshTitle()
|
|
|
}
|
|
|
})
|
|
|
</script>
|