fix
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
<script setup lang="ts">
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { showToast } from "vant";
|
||||
import { billApi } from "../../../generated/api";
|
||||
import { useModelApi } from "../../../shared/composables/useModelApi";
|
||||
import DocumentApprovalTasks from "../../../shared/components/DocumentApprovalTasks.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const billId = computed(() => Number(route.params.id));
|
||||
const activeTab = ref("info");
|
||||
|
||||
const {
|
||||
item: bill,
|
||||
loadingItem,
|
||||
error,
|
||||
retrieve: loadBill,
|
||||
} = useModelApi(billApi, {
|
||||
retrieveErrorMessage: "Не удалось загрузить счет",
|
||||
autoLoad: false,
|
||||
autoLoadOnFilterChange: false,
|
||||
});
|
||||
|
||||
type BillRelated = Record<string, unknown>;
|
||||
|
||||
function parseJson(value: unknown): unknown {
|
||||
if (typeof value === "string") {
|
||||
if (!value.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value) as unknown;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseObject(value: unknown): BillRelated | null {
|
||||
const parsed = parseJson(value);
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parsed as BillRelated;
|
||||
}
|
||||
|
||||
function objectLabel(value: unknown) {
|
||||
const record = parseObject(value);
|
||||
if (!record) {
|
||||
return "не указано";
|
||||
}
|
||||
|
||||
const candidates = ["name", "short_name", "number", "text", "full_name", "get_status_display"];
|
||||
for (const key of candidates) {
|
||||
const candidate = record[key];
|
||||
if (typeof candidate === "string" && candidate.trim()) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return "не указано";
|
||||
}
|
||||
|
||||
function money(value: number | null | undefined) {
|
||||
if (typeof value !== "number") {
|
||||
return "не указано";
|
||||
}
|
||||
|
||||
return new Intl.NumberFormat("ru-RU", {
|
||||
style: "currency",
|
||||
currency: "RUB",
|
||||
maximumFractionDigits: 2,
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
function formatValue(value: unknown) {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return "не указано";
|
||||
}
|
||||
|
||||
return String(value);
|
||||
}
|
||||
|
||||
async function openScan() {
|
||||
if (!bill.value?.scan) {
|
||||
showToast("У счета нет файла");
|
||||
return;
|
||||
}
|
||||
|
||||
await invoke("open_remote_file", { url: bill.value.scan });
|
||||
}
|
||||
|
||||
function relatedDocuments() {
|
||||
const raw = parseJson(bill.value?.transferdocument_set);
|
||||
if (!Array.isArray(raw)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (Number.isFinite(billId.value)) {
|
||||
loadBill(billId.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<van-notice-bar
|
||||
v-if="error"
|
||||
class="notice"
|
||||
color="#991b1b"
|
||||
background="#fee2e2"
|
||||
left-icon="warning-o"
|
||||
wrapable
|
||||
:scrollable="false"
|
||||
:text="error"
|
||||
/>
|
||||
|
||||
<section class="card detail-card">
|
||||
<van-loading v-if="loadingItem" class="state" type="spinner">Загрузка...</van-loading>
|
||||
|
||||
<van-empty v-else-if="!bill" description="Счет не найден" />
|
||||
|
||||
<template v-else>
|
||||
<div class="bill-summary">
|
||||
<div class="bill-summary__title">{{ bill.text || bill.number || `Счет #${bill.id}` }}</div>
|
||||
<div class="bill-summary__meta">
|
||||
№ {{ bill.number || 'не указан' }} · {{ bill.date_bill || bill.date }}
|
||||
</div>
|
||||
<div class="bill-summary__badges">
|
||||
<van-tag type="primary">{{ bill.status_name || bill.status }}</van-tag>
|
||||
<van-tag plain>{{ bill.contract_typ || 'не указан' }}</van-tag>
|
||||
</div>
|
||||
<van-cell-group inset>
|
||||
<van-cell>
|
||||
<template #title>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-label">Контрагент</div>
|
||||
<div class="detail-value">{{ objectLabel(bill.counterparty) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell>
|
||||
<template #title>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-label">Проект</div>
|
||||
<div class="detail-value">{{ objectLabel(bill.project) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell>
|
||||
<template #title>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-label">Договор</div>
|
||||
<div class="detail-value">{{ objectLabel(bill.contract) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell>
|
||||
<template #title>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-label">Сумма</div>
|
||||
<div class="detail-value detail-value--money">{{ money(bill.cost) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
|
||||
<van-tabs v-model:active="activeTab" shrink sticky class="bill-tabs">
|
||||
<van-tab name="info" title="Информация">
|
||||
<div class="tab-panel">
|
||||
<van-cell-group>
|
||||
<van-cell title="Номер" :value="formatValue(bill.number)" />
|
||||
<van-cell title="Описание" :value="formatValue(bill.text)" />
|
||||
<van-cell title="Дата счета" :value="formatValue(bill.date_bill)" />
|
||||
<van-cell title="Дата" :value="formatValue(bill.date)" />
|
||||
<van-cell title="Срок оплаты" :value="formatValue(bill.date_due)" />
|
||||
<van-cell title="Статус" :value="bill.status_name || bill.status" />
|
||||
<van-cell title="Тип" :value="formatValue(bill.contract_typ)" />
|
||||
<van-cell title="Комментарий" :value="formatValue(bill.comment)" />
|
||||
</van-cell-group>
|
||||
</div>
|
||||
</van-tab>
|
||||
|
||||
<van-tab name="finance" title="Финансы">
|
||||
<div class="tab-panel">
|
||||
<van-cell-group>
|
||||
<van-cell title="Сумма" :value="money(bill.cost)" />
|
||||
<van-cell title="Оплачено" :value="money(bill.paid)" />
|
||||
<van-cell title="К оплате" :value="money(bill.to_payd)" />
|
||||
<van-cell title="НДС" :value="money(bill.nds_cost)" />
|
||||
<van-cell title="Реестр" :value="bill.pp_maked ? 'Да' : 'Нет'" />
|
||||
<van-cell title="Архив" :value="bill.archive_s ? 'Да' : 'Нет'" />
|
||||
</van-cell-group>
|
||||
</div>
|
||||
</van-tab>
|
||||
|
||||
<van-tab name="relations" title="Связи">
|
||||
<div class="tab-panel">
|
||||
<van-cell-group>
|
||||
<van-cell title="Контрагент" :value="objectLabel(bill.counterparty)" />
|
||||
<van-cell title="Проект" :value="objectLabel(bill.project)" />
|
||||
<van-cell title="Договор" :value="objectLabel(bill.contract)" />
|
||||
<van-cell title="ФРЦ" :value="objectLabel(bill.frc)" />
|
||||
<van-cell title="Ответственный" :value="objectLabel(bill.responsible)" />
|
||||
<van-cell title="Автор" :value="objectLabel(bill.author)" />
|
||||
<van-cell title="Категория" :value="objectLabel(bill.category)" />
|
||||
</van-cell-group>
|
||||
|
||||
<van-cell-group v-if="relatedDocuments().length" title="Документы">
|
||||
<van-cell v-for="(doc, index) in relatedDocuments()" :key="String((doc as Record<string, unknown>).id ?? index)">
|
||||
<template #title>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-label">{{ String((doc as Record<string, unknown>).number ?? 'Документ') }}</div>
|
||||
<div class="detail-value">
|
||||
{{ String((doc as Record<string, unknown>).date ?? '') }}
|
||||
{{ (doc as Record<string, unknown>).cost ? `· ${formatValue((doc as Record<string, unknown>).cost)}` : '' }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
</van-tab>
|
||||
|
||||
<van-tab name="approval" title="Согласование">
|
||||
<div class="tab-panel">
|
||||
<DocumentApprovalTasks
|
||||
v-if="bill"
|
||||
:document-id="bill.id"
|
||||
filter-name="bill"
|
||||
title="Согласование счета"
|
||||
/>
|
||||
</div>
|
||||
</van-tab>
|
||||
|
||||
<van-tab name="file" title="Файл">
|
||||
<div class="tab-panel">
|
||||
<van-empty v-if="!bill.scan" description="Файл не прикреплен" />
|
||||
<div v-else class="form-actions stacked-actions">
|
||||
<van-button block round type="primary" plain @click="openScan">
|
||||
Открыть файл
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.bill-summary {
|
||||
padding: 16px 16px 10px;
|
||||
}
|
||||
|
||||
.bill-summary__title {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.bill-summary__meta {
|
||||
margin-top: 4px;
|
||||
color: var(--van-text-color-2);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.bill-summary__badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user