This commit is contained in:
che
2026-07-23 12:19:46 +05:00
parent dddf6516d3
commit 42029533fd
12 changed files with 603 additions and 11 deletions
+5
View File
@@ -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");
+62
View File
@@ -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;
+68 -4
View File
@@ -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>
+5 -1
View File
@@ -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 {