fix
This commit is contained in:
@@ -4,6 +4,10 @@ import type {
|
||||
ContractCreate,
|
||||
ContractUpdate,
|
||||
ContractListParams,
|
||||
ContractApplicationFile,
|
||||
ContractApplicationFileCreate,
|
||||
ContractApplicationFileUpdate,
|
||||
ContractApplicationFileListParams,
|
||||
Counterparty,
|
||||
CounterpartyCreate,
|
||||
CounterpartyUpdate,
|
||||
@@ -35,6 +39,7 @@ import type {
|
||||
} from "./models";
|
||||
|
||||
export const contractApi = createModelApi<Contract, ContractCreate, ContractUpdate, ContractListParams>("contract");
|
||||
export const contractApplicationFileApi = createModelApi<ContractApplicationFile, ContractApplicationFileCreate, ContractApplicationFileUpdate, ContractApplicationFileListParams>("contract_application_file");
|
||||
export const counterpartyApi = createModelApi<Counterparty, CounterpartyCreate, CounterpartyUpdate, CounterpartyListParams>("counterparty");
|
||||
export const employeeApi = createModelApi<Employee, EmployeeCreate, EmployeeUpdate, EmployeeListParams>("employee");
|
||||
export const frcApi = createModelApi<Frc, FrcCreate, FrcUpdate, FrcListParams>("frc");
|
||||
|
||||
@@ -120,6 +120,68 @@ export interface ContractListParams extends ListParams {
|
||||
employee_id?: number | null;
|
||||
}
|
||||
|
||||
export interface ContractApplicationFile {
|
||||
id: number;
|
||||
contract_id: number;
|
||||
name: string;
|
||||
file_type: string;
|
||||
file_type_display: string;
|
||||
status: string;
|
||||
status_display: string;
|
||||
absolute_url: string;
|
||||
scan_name: string;
|
||||
scan_url: string;
|
||||
local_path: string;
|
||||
comment: string;
|
||||
bill_total: number;
|
||||
bill_cost_total: number;
|
||||
questionnair: number | null;
|
||||
contract: Contract;
|
||||
}
|
||||
|
||||
export interface ContractApplicationFileCreate {
|
||||
contract_id: number;
|
||||
name: string;
|
||||
file_type: string;
|
||||
file_type_display: string;
|
||||
status: string;
|
||||
status_display: string;
|
||||
absolute_url: string;
|
||||
scan_name: string;
|
||||
scan_url: string;
|
||||
local_path: string;
|
||||
comment: string;
|
||||
bill_total: number;
|
||||
bill_cost_total: number;
|
||||
questionnair?: number | null;
|
||||
}
|
||||
|
||||
export interface ContractApplicationFileUpdate {
|
||||
contract_id?: number;
|
||||
name?: string;
|
||||
file_type?: string;
|
||||
file_type_display?: string;
|
||||
status?: string;
|
||||
status_display?: string;
|
||||
absolute_url?: string;
|
||||
scan_name?: string;
|
||||
scan_url?: string;
|
||||
local_path?: string;
|
||||
comment?: string;
|
||||
bill_total?: number;
|
||||
bill_cost_total?: number;
|
||||
questionnair?: number | null;
|
||||
}
|
||||
|
||||
export interface ContractApplicationFileListParams extends ListParams {
|
||||
id?: number;
|
||||
contract_id?: number;
|
||||
name?: string;
|
||||
name__contains?: string;
|
||||
file_type?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface Counterparty {
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { openPath } from "@tauri-apps/plugin-opener";
|
||||
import { computed, onMounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { contractApi } from "../generated/api";
|
||||
import type { Contract } from "../generated/models";
|
||||
import { showToast } from "vant";
|
||||
import { contractApi, contractApplicationFileApi } from "../generated/api";
|
||||
import type { Contract, ContractApplicationFileListParams } from "../generated/models";
|
||||
import { useModelApi } from "../composables/useModelApi";
|
||||
|
||||
const route = useRoute();
|
||||
@@ -19,6 +21,19 @@ const {
|
||||
autoLoad: false,
|
||||
autoLoadOnFilterChange: false,
|
||||
});
|
||||
const {
|
||||
items: applicationFiles,
|
||||
loading: loadingApplicationFiles,
|
||||
error: applicationFilesError,
|
||||
load: loadApplicationFiles,
|
||||
} = useModelApi(contractApplicationFileApi, {
|
||||
defaultListParams: { ordering: "id" } as ContractApplicationFileListParams,
|
||||
loadErrorMessage: "Не удалось загрузить приложения",
|
||||
autoLoad: false,
|
||||
autoLoadOnFilterChange: false,
|
||||
});
|
||||
|
||||
const viewError = computed(() => error.value || applicationFilesError.value);
|
||||
|
||||
type DetailField = {
|
||||
title: string;
|
||||
@@ -87,23 +102,48 @@ function formatValue(value: unknown) {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
async function openApplicationFile(localPath: string) {
|
||||
if (!localPath) {
|
||||
showToast("Файл не скачан");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await openPath(localPath);
|
||||
} catch (err) {
|
||||
showToast(errorMessage(err, "Не удалось открыть файл"));
|
||||
}
|
||||
}
|
||||
|
||||
function errorMessage(err: unknown, fallback: string) {
|
||||
if (err instanceof Error) {
|
||||
return err.message;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (Number.isFinite(contractId.value)) {
|
||||
loadContract(contractId.value);
|
||||
loadApplicationFiles({
|
||||
ordering: "id",
|
||||
contract_id: contractId.value,
|
||||
} as ContractApplicationFileListParams);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<van-notice-bar
|
||||
v-if="error"
|
||||
v-if="viewError"
|
||||
class="notice"
|
||||
color="#991b1b"
|
||||
background="#fee2e2"
|
||||
left-icon="warning-o"
|
||||
wrapable
|
||||
:scrollable="false"
|
||||
:text="error"
|
||||
:text="viewError"
|
||||
/>
|
||||
|
||||
<section class="card detail-card">
|
||||
@@ -139,6 +179,30 @@ onMounted(() => {
|
||||
/>
|
||||
</van-cell-group>
|
||||
|
||||
<van-cell-group inset title="Приложения">
|
||||
<van-cell v-if="loadingApplicationFiles" title="Загрузка приложений..." />
|
||||
<van-cell v-else-if="applicationFiles.length === 0" title="Приложений нет" />
|
||||
<van-cell
|
||||
v-for="file in applicationFiles"
|
||||
v-else
|
||||
:key="file.id"
|
||||
:title="file.name"
|
||||
:label="`${file.file_type_display} · ${file.status_display} · ${file.scan_name}`"
|
||||
>
|
||||
<template #value>
|
||||
<van-button
|
||||
size="small"
|
||||
type="primary"
|
||||
plain
|
||||
:disabled="!file.local_path"
|
||||
@click="openApplicationFile(file.local_path)"
|
||||
>
|
||||
Открыть
|
||||
</van-button>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
|
||||
<div class="detail-actions">
|
||||
<van-button block round type="primary" plain @click="router.back()">Назад</van-button>
|
||||
</div>
|
||||
|
||||
@@ -59,6 +59,8 @@ const currentPage = computed({
|
||||
interface SyncContractsResult {
|
||||
synced: number;
|
||||
pages: number;
|
||||
applications: number;
|
||||
files_downloaded: number;
|
||||
}
|
||||
|
||||
const syncing = ref(false);
|
||||
@@ -68,7 +70,9 @@ async function syncContracts() {
|
||||
|
||||
try {
|
||||
const result = await invoke<SyncContractsResult>("sync_contracts");
|
||||
showToast(`Синхронизировано: ${result.synced}`);
|
||||
showToast(
|
||||
`Синхронизировано: ${result.synced}; приложений: ${result.applications}; файлов: ${result.files_downloaded}`,
|
||||
);
|
||||
if (filters.offset) {
|
||||
filters.offset = 0;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user