fix
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
use che_tauri::{Filter, FilterSet};
|
use che_tauri::{Filter, FilterSet};
|
||||||
|
|
||||||
use super::models::{Employee, Message, Task};
|
use super::models::{Employee, Message, Task, TaskTransfer};
|
||||||
|
|
||||||
static EMPLOYEE_FILTERS: &[Filter] = &[
|
static EMPLOYEE_FILTERS: &[Filter] = &[
|
||||||
Filter::exact("id"),
|
Filter::exact("id"),
|
||||||
@@ -24,6 +24,14 @@ static MESSAGE_FILTERS: &[Filter] = &[
|
|||||||
Filter::exact("employee_id"),
|
Filter::exact("employee_id"),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static TASK_TRANSFER_FILTERS: &[Filter] = &[
|
||||||
|
Filter::exact("id"),
|
||||||
|
Filter::exact("employee_from_id"),
|
||||||
|
Filter::exact("employee_to_id"),
|
||||||
|
Filter::exact("status"),
|
||||||
|
Filter::exact("typ"),
|
||||||
|
];
|
||||||
|
|
||||||
pub fn employee_filterset() -> FilterSet<Employee> {
|
pub fn employee_filterset() -> FilterSet<Employee> {
|
||||||
FilterSet::new(EMPLOYEE_FILTERS)
|
FilterSet::new(EMPLOYEE_FILTERS)
|
||||||
}
|
}
|
||||||
@@ -35,3 +43,7 @@ pub fn task_filterset() -> FilterSet<Task> {
|
|||||||
pub fn message_filterset() -> FilterSet<Message> {
|
pub fn message_filterset() -> FilterSet<Message> {
|
||||||
FilterSet::new(MESSAGE_FILTERS)
|
FilterSet::new(MESSAGE_FILTERS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn task_transfer_filterset() -> FilterSet<TaskTransfer> {
|
||||||
|
FilterSet::new(TASK_TRANSFER_FILTERS)
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,11 +21,18 @@ impl AppModule for TaskModule {
|
|||||||
serializers::employee_serializer(),
|
serializers::employee_serializer(),
|
||||||
filters::employee_filterset(),
|
filters::employee_filterset(),
|
||||||
);
|
);
|
||||||
ctx.resource::<models::Task>(
|
ctx.remote_resource::<models::Task>(
|
||||||
"task",
|
"task",
|
||||||
|
"/api/persone/task",
|
||||||
serializers::task_serializer(),
|
serializers::task_serializer(),
|
||||||
filters::task_filterset(),
|
filters::task_filterset(),
|
||||||
);
|
);
|
||||||
|
ctx.remote_resource::<models::TaskTransfer>(
|
||||||
|
"task_transfer",
|
||||||
|
"/api/persone/task_transfer",
|
||||||
|
serializers::task_transfer_serializer(),
|
||||||
|
filters::task_transfer_filterset(),
|
||||||
|
);
|
||||||
ctx.resource::<models::Message>(
|
ctx.resource::<models::Message>(
|
||||||
"message",
|
"message",
|
||||||
serializers::message_serializer(),
|
serializers::message_serializer(),
|
||||||
|
|||||||
@@ -42,3 +42,22 @@ pub struct Message {
|
|||||||
|
|
||||||
pub text: String,
|
pub text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Model)]
|
||||||
|
#[model(table = "task_transfer")]
|
||||||
|
pub struct TaskTransfer {
|
||||||
|
#[field(primary_key)]
|
||||||
|
pub id: i64,
|
||||||
|
|
||||||
|
#[field(foreign_key = Employee)]
|
||||||
|
pub employee_from_id: Option<i64>,
|
||||||
|
|
||||||
|
#[field(foreign_key = Employee)]
|
||||||
|
pub employee_to_id: Option<i64>,
|
||||||
|
|
||||||
|
pub date_create: String,
|
||||||
|
|
||||||
|
pub status: String,
|
||||||
|
|
||||||
|
pub typ: String,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use che_tauri::{Field, ModelSerializer};
|
use che_tauri::{Field, ModelSerializer};
|
||||||
|
|
||||||
use super::models::{Employee, Message, Task};
|
use super::models::{Employee, Message, Task, TaskTransfer};
|
||||||
|
|
||||||
static EMPLOYEE_FIELDS: &[Field] = &[
|
static EMPLOYEE_FIELDS: &[Field] = &[
|
||||||
Field::new("id").read_only(),
|
Field::new("id").read_only(),
|
||||||
@@ -23,6 +23,15 @@ static MESSAGE_FIELDS: &[Field] = &[
|
|||||||
Field::new("text"),
|
Field::new("text"),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static TASK_TRANSFER_FIELDS: &[Field] = &[
|
||||||
|
Field::new("id").read_only(),
|
||||||
|
Field::new("employee_from_id").required(false).nullable(),
|
||||||
|
Field::new("employee_to_id").required(false).nullable(),
|
||||||
|
Field::new("date_create").read_only(),
|
||||||
|
Field::new("status"),
|
||||||
|
Field::new("typ"),
|
||||||
|
];
|
||||||
|
|
||||||
pub fn employee_serializer() -> ModelSerializer<Employee> {
|
pub fn employee_serializer() -> ModelSerializer<Employee> {
|
||||||
ModelSerializer::new(EMPLOYEE_FIELDS)
|
ModelSerializer::new(EMPLOYEE_FIELDS)
|
||||||
}
|
}
|
||||||
@@ -34,3 +43,7 @@ pub fn task_serializer() -> ModelSerializer<Task> {
|
|||||||
pub fn message_serializer() -> ModelSerializer<Message> {
|
pub fn message_serializer() -> ModelSerializer<Message> {
|
||||||
ModelSerializer::new(MESSAGE_FIELDS)
|
ModelSerializer::new(MESSAGE_FIELDS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn task_transfer_serializer() -> ModelSerializer<TaskTransfer> {
|
||||||
|
ModelSerializer::new(TASK_TRANSFER_FIELDS)
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ use tauri::Manager;
|
|||||||
|
|
||||||
use crate::sync::SyncContractsResult;
|
use crate::sync::SyncContractsResult;
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct CurrentEmployee {
|
||||||
|
id: i64,
|
||||||
|
name: String,
|
||||||
|
short_name: String,
|
||||||
|
avatar: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn greet(name: &str) -> String {
|
fn greet(name: &str) -> String {
|
||||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||||
@@ -43,6 +51,45 @@ fn auth_status(api: tauri::State<'_, TauriApi>) -> bool {
|
|||||||
api.is_authenticated()
|
api.is_authenticated()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn current_employee(api: tauri::State<'_, TauriApi>) -> Result<CurrentEmployee, ApiError> {
|
||||||
|
let remote = api
|
||||||
|
.state()
|
||||||
|
.config
|
||||||
|
.remote
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| ApiError::bad_request("current_employee requires [remote].base_url config"))?;
|
||||||
|
|
||||||
|
let token = api
|
||||||
|
.state()
|
||||||
|
.auth_token()
|
||||||
|
.ok_or_else(|| ApiError::new("not_authenticated", "authentication token is missing"))?;
|
||||||
|
|
||||||
|
let response = reqwest::Client::new()
|
||||||
|
.get(format!(
|
||||||
|
"{}/api/persone/employee/who_im/",
|
||||||
|
remote.base_url.trim_end_matches('/')
|
||||||
|
))
|
||||||
|
.header(reqwest::header::AUTHORIZATION, format!("Token {token}"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|error| ApiError::new("remote_error", error.to_string()))?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
let status = response.status();
|
||||||
|
let detail = response.text().await.unwrap_or_default();
|
||||||
|
return Err(ApiError::new(
|
||||||
|
"remote_error",
|
||||||
|
format!("who_im request failed with {status}: {detail}"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
.json::<CurrentEmployee>()
|
||||||
|
.await
|
||||||
|
.map_err(|error| ApiError::new("remote_error", error.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn sync_contracts(
|
async fn sync_contracts(
|
||||||
app: tauri::AppHandle,
|
app: tauri::AppHandle,
|
||||||
@@ -116,6 +163,7 @@ pub fn run() {
|
|||||||
auth_set_token,
|
auth_set_token,
|
||||||
auth_logout,
|
auth_logout,
|
||||||
auth_status,
|
auth_status,
|
||||||
|
current_employee,
|
||||||
sync_contracts,
|
sync_contracts,
|
||||||
load_application_file
|
load_application_file
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -1,45 +1,91 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { showToast } from "vant";
|
import { showToast } from "vant";
|
||||||
import { taskApi } from "../../../generated/api";
|
import { employeeApi, taskApi } from "../../../generated/api";
|
||||||
|
import type { Employee, TaskCreate, TaskPersonInput } from "../../../generated/models";
|
||||||
import { useModelApi } from "../../../shared/composables/useModelApi";
|
import { useModelApi } from "../../../shared/composables/useModelApi";
|
||||||
import EmployeeSelect from "../../personnel/components/EmployeeSelect.vue";
|
import EmployeeSelect from "../../personnel/components/EmployeeSelect.vue";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const newTaskName = ref("");
|
const deadline = ref("");
|
||||||
const selectedAuthorId = ref(0);
|
const taskText = ref("");
|
||||||
|
const selectedDoerId = ref(0);
|
||||||
const selectedResponsibleId = ref(0);
|
const selectedResponsibleId = ref(0);
|
||||||
|
|
||||||
const {
|
const { creating, error, create: createTaskApi } = useModelApi(taskApi, {
|
||||||
creating,
|
|
||||||
error,
|
|
||||||
create: createTaskApi,
|
|
||||||
} = useModelApi(taskApi, {
|
|
||||||
createErrorMessage: "Не удалось создать задачу",
|
createErrorMessage: "Не удалось создать задачу",
|
||||||
autoLoad: false,
|
autoLoad: false,
|
||||||
autoLoadOnFilterChange: false,
|
autoLoadOnFilterChange: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { retrieve: loadEmployee } = useModelApi(employeeApi, {
|
||||||
|
autoLoad: false,
|
||||||
|
autoLoadOnFilterChange: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const canSubmit = computed(() => Boolean(deadline.value.trim()) && Boolean(selectedDoerId.value));
|
||||||
|
|
||||||
|
function makeShortName(name: string) {
|
||||||
|
const parts = name.trim().split(/\s+/).filter(Boolean);
|
||||||
|
|
||||||
|
if (parts.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts.length === 1) {
|
||||||
|
return parts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${parts[0]} ${parts.slice(1).map((part) => `${part[0]?.toUpperCase()}.`).join(" ")}`.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildPerson(id: number): Promise<TaskPersonInput | null> {
|
||||||
|
if (!id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const employee = await loadEmployee(id);
|
||||||
|
return employeeToTaskPerson(employee);
|
||||||
|
}
|
||||||
|
|
||||||
|
function employeeToTaskPerson(employee: Employee): TaskPersonInput {
|
||||||
|
return {
|
||||||
|
id: employee.id,
|
||||||
|
name: employee.name,
|
||||||
|
short_name: makeShortName(employee.name),
|
||||||
|
avatar_small: employee.avatar_small,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function createTask() {
|
async function createTask() {
|
||||||
const name = newTaskName.value.trim();
|
if (!canSubmit.value) {
|
||||||
if (!name) {
|
showToast("Выберите исполнителя и срок");
|
||||||
showToast("Введите название задачи");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const task = await createTaskApi({
|
const doer = await buildPerson(selectedDoerId.value);
|
||||||
name,
|
if (!doer) {
|
||||||
author_id: selectedAuthorId.value || null,
|
showToast("Не удалось загрузить исполнителя");
|
||||||
responsible_id: selectedResponsibleId.value || null,
|
return;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const responsible = await buildPerson(selectedResponsibleId.value);
|
||||||
|
|
||||||
|
const payload: TaskCreate = {
|
||||||
|
doer,
|
||||||
|
deadline: deadline.value,
|
||||||
|
text: taskText.value.trim() || undefined,
|
||||||
|
responsible,
|
||||||
|
};
|
||||||
|
|
||||||
|
const task = await createTaskApi(payload);
|
||||||
|
|
||||||
if (task) {
|
if (task) {
|
||||||
showToast("Задача создана");
|
showToast("Задача создана");
|
||||||
router.push("/tasks");
|
router.push(`/tasks/${task.id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -55,24 +101,40 @@ async function createTask() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<van-form class="card" @submit="createTask">
|
<van-form class="card" @submit="createTask">
|
||||||
<van-field
|
<van-field label="Исполнитель" required>
|
||||||
v-model="newTaskName"
|
|
||||||
name="name"
|
|
||||||
label="Название"
|
|
||||||
placeholder="Например: Купить молоко"
|
|
||||||
clearable
|
|
||||||
:disabled="creating"
|
|
||||||
/>
|
|
||||||
<van-field label="Автор">
|
|
||||||
<template #input>
|
<template #input>
|
||||||
<EmployeeSelect v-model="selectedAuthorId" />
|
<EmployeeSelect v-model="selectedDoerId" />
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
|
|
||||||
<van-field label="Ответственный">
|
<van-field label="Ответственный">
|
||||||
<template #input>
|
<template #input>
|
||||||
<EmployeeSelect v-model="selectedResponsibleId" />
|
<EmployeeSelect v-model="selectedResponsibleId" />
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
|
|
||||||
|
<van-field
|
||||||
|
v-model="deadline"
|
||||||
|
name="deadline"
|
||||||
|
label="Срок"
|
||||||
|
type="date"
|
||||||
|
required
|
||||||
|
clearable
|
||||||
|
:disabled="creating"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<van-field
|
||||||
|
v-model="taskText"
|
||||||
|
name="text"
|
||||||
|
label="Текст"
|
||||||
|
type="textarea"
|
||||||
|
rows="3"
|
||||||
|
autosize
|
||||||
|
clearable
|
||||||
|
placeholder="Описание задачи"
|
||||||
|
:disabled="creating"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="form-actions stacked-actions">
|
<div class="form-actions stacked-actions">
|
||||||
<van-button
|
<van-button
|
||||||
block
|
block
|
||||||
@@ -80,12 +142,11 @@ async function createTask() {
|
|||||||
type="primary"
|
type="primary"
|
||||||
native-type="submit"
|
native-type="submit"
|
||||||
:loading="creating"
|
:loading="creating"
|
||||||
:disabled="!newTaskName.trim()"
|
:disabled="!canSubmit"
|
||||||
>
|
>
|
||||||
Создать задачу
|
Создать задачу
|
||||||
</van-button>
|
</van-button>
|
||||||
<van-button block round plain type="default" @click="router.back()">Отмена</van-button>
|
<van-button block round plain type="default" @click="router.back()">Отмена</van-button>
|
||||||
</div>
|
</div>
|
||||||
</van-form>
|
</van-form>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { showToast } from "vant";
|
import { taskApi } from "../../../generated/api";
|
||||||
import { employeeApi, messageApi, taskApi } from "../../../generated/api";
|
|
||||||
import { useModelApi } from "../../../shared/composables/useModelApi";
|
import { useModelApi } from "../../../shared/composables/useModelApi";
|
||||||
import EmployeeSelect from "../../personnel/components/EmployeeSelect.vue";
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const taskId = computed(() => Number(route.params.id));
|
const taskId = computed(() => Number(route.params.id));
|
||||||
const messageText = ref("");
|
|
||||||
const selectedEmployeeId = ref(0);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
item: task,
|
item: task,
|
||||||
@@ -22,80 +18,43 @@ const {
|
|||||||
autoLoad: false,
|
autoLoad: false,
|
||||||
autoLoadOnFilterChange: false,
|
autoLoadOnFilterChange: false,
|
||||||
});
|
});
|
||||||
const {
|
function personName(person: { short_name?: string | null; name?: string | null } | null) {
|
||||||
items: messages,
|
if (!person) {
|
||||||
loading: loadingMessages,
|
return "не указан";
|
||||||
creating: sendingMessage,
|
|
||||||
error: messageError,
|
|
||||||
load: loadMessages,
|
|
||||||
create: createMessageApi,
|
|
||||||
} = useModelApi(messageApi, {
|
|
||||||
loadErrorMessage: "Не удалось загрузить сообщения",
|
|
||||||
createErrorMessage: "Не удалось отправить сообщение",
|
|
||||||
autoLoad: false,
|
|
||||||
autoLoadOnFilterChange: false,
|
|
||||||
});
|
|
||||||
const { items: employees } = useModelApi(employeeApi, {
|
|
||||||
defaultListParams: { ordering: "id" },
|
|
||||||
loadErrorMessage: "Не удалось загрузить сотрудников",
|
|
||||||
});
|
|
||||||
|
|
||||||
async function sendMessage() {
|
|
||||||
if (!Number.isFinite(taskId.value)) {
|
|
||||||
showToast("Некорректный ID задачи");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = messageText.value.trim();
|
return person.short_name ?? person.name ?? "не указан";
|
||||||
if (!text) {
|
|
||||||
showToast("Введите сообщение");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!selectedEmployeeId.value) {
|
|
||||||
showToast("Выберите сотрудника");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = await createMessageApi({
|
|
||||||
task_id: taskId.value,
|
|
||||||
employee_id: selectedEmployeeId.value,
|
|
||||||
text,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
messageText.value = "";
|
|
||||||
await loadMessages({ task_id: taskId.value });
|
|
||||||
showToast("Сообщение отправлено");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function employeeName(id: number) {
|
function taskTitle() {
|
||||||
return employees.value.find((employee) => employee.id === id)?.name ?? `#${id}`;
|
return task.value?.text?.trim() || task.value?.result?.trim() || `Задача #${taskId.value}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function employeeAvatar(id: number) {
|
function formatDate(value: string | null | undefined) {
|
||||||
return employees.value.find((employee) => employee.id === id)?.avatar_small ?? "";
|
return value || "не указано";
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBoolean(value: boolean) {
|
||||||
|
return value ? "Да" : "Нет";
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (Number.isFinite(taskId.value)) {
|
if (Number.isFinite(taskId.value)) {
|
||||||
loadTask(taskId.value);
|
loadTask(taskId.value);
|
||||||
loadMessages({ task_id: taskId.value });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<van-notice-bar
|
<van-notice-bar
|
||||||
v-if="error || messageError"
|
v-if="error"
|
||||||
class="notice"
|
class="notice"
|
||||||
color="#991b1b"
|
color="#991b1b"
|
||||||
background="#fee2e2"
|
background="#fee2e2"
|
||||||
left-icon="warning-o"
|
left-icon="warning-o"
|
||||||
wrapable
|
wrapable
|
||||||
:scrollable="false"
|
:scrollable="false"
|
||||||
:text="error || messageError"
|
:text="error"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section class="card detail-card">
|
<section class="card detail-card">
|
||||||
@@ -106,9 +65,21 @@ onMounted(() => {
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<van-cell-group inset>
|
<van-cell-group inset>
|
||||||
<van-cell title="ID" :value="task.id" />
|
<van-cell title="ID" :value="task.id" />
|
||||||
<van-cell title="Название" :value="task.name" />
|
<van-cell title="Заголовок" :value="taskTitle()" />
|
||||||
<van-cell title="Автор" :value="task.author_id ?? 'не указан'" />
|
<van-cell title="Исполнитель" :value="personName(task.doer)" />
|
||||||
<van-cell title="Ответственный" :value="task.responsible_id ?? 'не указан'" />
|
<van-cell title="Автор" :value="personName(task.author)" />
|
||||||
|
<van-cell title="Ответственный" :value="personName(task.responsible)" />
|
||||||
|
<van-cell title="Статус" :value="task.get_status" />
|
||||||
|
<van-cell title="Срок" :value="formatDate(task.deadline)" />
|
||||||
|
<van-cell title="Плановая дата" :value="formatDate(task.plan_date)" />
|
||||||
|
<van-cell title="Запрос новой даты" :value="formatDate(task.request_new_deadline)" />
|
||||||
|
<van-cell title="Приоритет" :value="task.priority ?? 'не указан'" />
|
||||||
|
<van-cell title="Тип" :value="task.typ || 'не указан'" />
|
||||||
|
<van-cell title="Результат" :value="task.result || 'не указан'" />
|
||||||
|
<van-cell title="Комментарий" :value="task.comment || 'не указан'" />
|
||||||
|
<van-cell title="Примечание" :value="task.note || 'не указано'" />
|
||||||
|
<van-cell title="Согласование" :value="formatBoolean(task.approve)" />
|
||||||
|
<van-cell title="Архив" :value="formatBoolean(task.archive)" />
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
</template>
|
</template>
|
||||||
</section>
|
</section>
|
||||||
@@ -116,73 +87,25 @@ onMounted(() => {
|
|||||||
<section class="card messages-card">
|
<section class="card messages-card">
|
||||||
<div class="list-title">
|
<div class="list-title">
|
||||||
<h2>Диалог</h2>
|
<h2>Диалог</h2>
|
||||||
<van-button
|
|
||||||
size="small"
|
|
||||||
plain
|
|
||||||
type="primary"
|
|
||||||
:loading="loadingMessages"
|
|
||||||
@click="loadMessages({ task_id: taskId })"
|
|
||||||
>
|
|
||||||
Обновить
|
|
||||||
</van-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<van-loading v-if="loadingMessages && messages.length === 0" class="state" type="spinner">
|
<van-empty v-if="!task?.message_set?.length" description="Сообщений пока нет" />
|
||||||
Загрузка сообщений...
|
|
||||||
</van-loading>
|
|
||||||
|
|
||||||
<van-empty v-else-if="messages.length === 0" description="Сообщений пока нет" />
|
|
||||||
|
|
||||||
<div v-else class="message-list">
|
<div v-else class="message-list">
|
||||||
<article v-for="message in messages" :key="message.id" class="message-item">
|
<article v-for="message in task.message_set" :key="message.id" class="message-item">
|
||||||
<van-image
|
<van-image class="message-avatar" round width="40" height="40" :src="message.author.avatar_small ?? ''" />
|
||||||
class="message-avatar"
|
|
||||||
round
|
|
||||||
width="40"
|
|
||||||
height="40"
|
|
||||||
:src="employeeAvatar(message.employee_id)"
|
|
||||||
/>
|
|
||||||
<div class="message-bubble">
|
<div class="message-bubble">
|
||||||
<div class="message-author">{{ employeeName(message.employee_id) }}</div>
|
<div class="message-author">{{ personName(message.author) }}</div>
|
||||||
<div class="message-text">{{ message.text }}</div>
|
<div class="message-text">{{ message.text }}</div>
|
||||||
|
<div class="message-meta">
|
||||||
|
{{ message.date }} · {{ message.status }} · {{ personName(message.recipient) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<van-form class="card" @submit="sendMessage">
|
<div class="form-actions stacked-actions">
|
||||||
<van-field
|
<van-button block round type="primary" plain @click="router.back()">Назад</van-button>
|
||||||
name="employee_id"
|
</div>
|
||||||
label="Отправитель"
|
|
||||||
placeholder="Выберите сотрудника"
|
|
||||||
>
|
|
||||||
<template #input>
|
|
||||||
<EmployeeSelect v-model="selectedEmployeeId" />
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
<van-field
|
|
||||||
v-model="messageText"
|
|
||||||
name="text"
|
|
||||||
label="Сообщение"
|
|
||||||
type="textarea"
|
|
||||||
rows="2"
|
|
||||||
autosize
|
|
||||||
clearable
|
|
||||||
placeholder="Напишите сообщение"
|
|
||||||
:disabled="sendingMessage"
|
|
||||||
/>
|
|
||||||
<div class="form-actions stacked-actions">
|
|
||||||
<van-button
|
|
||||||
block
|
|
||||||
round
|
|
||||||
type="primary"
|
|
||||||
native-type="submit"
|
|
||||||
:loading="sendingMessage"
|
|
||||||
:disabled="!Number.isFinite(taskId) || !messageText.trim() || !selectedEmployeeId"
|
|
||||||
>
|
|
||||||
Отправить сообщение
|
|
||||||
</van-button>
|
|
||||||
<van-button block round type="primary" plain @click="router.back()">Назад</van-button>
|
|
||||||
</div>
|
|
||||||
</van-form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,78 +1,377 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { employeeApi, taskApi } from "../../../generated/api";
|
import { taskApi, taskTransferApi } from "../../../generated/api";
|
||||||
|
import type {
|
||||||
|
Task,
|
||||||
|
TaskCreate,
|
||||||
|
TaskListParams,
|
||||||
|
TaskTransfer,
|
||||||
|
TaskTransferListParams,
|
||||||
|
TaskUpdate,
|
||||||
|
} from "../../../generated/models";
|
||||||
import { useModelApi } from "../../../shared/composables/useModelApi";
|
import { useModelApi } from "../../../shared/composables/useModelApi";
|
||||||
|
|
||||||
|
type CurrentEmployee = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
short_name: string;
|
||||||
|
avatar?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MainTabKey = "incoming" | "transfer" | "review" | "outgoing" | "archive";
|
||||||
|
type DocTabKey =
|
||||||
|
| "all"
|
||||||
|
| "simple"
|
||||||
|
| "memo"
|
||||||
|
| "bill"
|
||||||
|
| "contract"
|
||||||
|
| "contract_application"
|
||||||
|
| "entry_letter"
|
||||||
|
| "outgoing_letter";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const mainTab = ref<MainTabKey>("incoming");
|
||||||
|
const docTab = ref<DocTabKey>("all");
|
||||||
|
const employee = ref<CurrentEmployee | null>(null);
|
||||||
|
const employeeError = ref("");
|
||||||
|
const employeeLoading = ref(false);
|
||||||
|
const refreshing = ref(false);
|
||||||
|
const switchingList = ref(false);
|
||||||
|
let suppressDocTabReload = false;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
items: tasks,
|
items: tasks,
|
||||||
loading,
|
loading: taskLoading,
|
||||||
error,
|
error: taskError,
|
||||||
load: loadTasks,
|
load: loadTasks,
|
||||||
} = useModelApi(taskApi, {
|
} = useModelApi<Task, TaskCreate, TaskUpdate, TaskListParams>(taskApi, {
|
||||||
defaultListParams: { ordering: "id" },
|
defaultListParams: { ordering: "-id" },
|
||||||
loadErrorMessage: "Не удалось загрузить задачи",
|
loadErrorMessage: "Не удалось загрузить задачи",
|
||||||
});
|
});
|
||||||
const { items: employees } = useModelApi(employeeApi, {
|
|
||||||
defaultListParams: { ordering: "id" },
|
const {
|
||||||
loadErrorMessage: "Не удалось загрузить сотрудников",
|
items: taskTransfers,
|
||||||
|
loading: taskTransferLoading,
|
||||||
|
error: taskTransferError,
|
||||||
|
load: loadTaskTransfers,
|
||||||
|
} = useModelApi<
|
||||||
|
TaskTransfer,
|
||||||
|
Partial<TaskTransfer>,
|
||||||
|
Partial<TaskTransfer>,
|
||||||
|
TaskTransferListParams
|
||||||
|
>(taskTransferApi, {
|
||||||
|
defaultListParams: { ordering: "-id" },
|
||||||
|
loadErrorMessage: "Не удалось загрузить переносы срока",
|
||||||
});
|
});
|
||||||
|
|
||||||
function employeeName(id: number | null) {
|
const mainTabs = [
|
||||||
if (id === null || id === 0) {
|
{ key: "incoming", label: "Входящие", icon: "todo-list-o" },
|
||||||
|
{ key: "transfer", label: "Перенос срока", icon: "underway-o" },
|
||||||
|
{ key: "review", label: "Для проверки", icon: "eye-o" },
|
||||||
|
{ key: "outgoing", label: "Исходящие", icon: "send-gift-o" },
|
||||||
|
{ key: "archive", label: "Архив", icon: "folder-o" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const docTabs = [
|
||||||
|
{ key: "all", label: "Все" },
|
||||||
|
{ key: "simple", label: "Простые" },
|
||||||
|
{ key: "memo", label: "Служебные" },
|
||||||
|
{ key: "bill", label: "Счета" },
|
||||||
|
{ key: "contract", label: "Договоры" },
|
||||||
|
{ key: "contract_application", label: "Приложения" },
|
||||||
|
{ key: "entry_letter", label: "Входящие письма" },
|
||||||
|
{ key: "outgoing_letter", label: "Исходящие письма" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const visibleItems = computed(() =>
|
||||||
|
mainTab.value === "transfer" ? taskTransfers.value : tasks.value,
|
||||||
|
);
|
||||||
|
const activeLoading = computed(() =>
|
||||||
|
mainTab.value === "transfer" ? taskTransferLoading.value : taskLoading.value,
|
||||||
|
);
|
||||||
|
const showEmployeeLoading = computed(
|
||||||
|
() =>
|
||||||
|
employeeLoading.value &&
|
||||||
|
visibleItems.value.length === 0 &&
|
||||||
|
!switchingList.value,
|
||||||
|
);
|
||||||
|
const activeError = computed(() =>
|
||||||
|
mainTab.value === "transfer" ? taskTransferError.value : taskError.value,
|
||||||
|
);
|
||||||
|
const showDocTabs = computed(() => mainTab.value !== "transfer");
|
||||||
|
|
||||||
|
function personName(
|
||||||
|
person: { short_name?: string | null; name?: string | null } | null,
|
||||||
|
) {
|
||||||
|
if (!person) {
|
||||||
return "не указан";
|
return "не указан";
|
||||||
}
|
}
|
||||||
|
|
||||||
return employees.value.find((employee) => employee.id === id)?.name ?? `#${id}`;
|
return person.short_name ?? person.name ?? "не указан";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function taskTitle(task: {
|
||||||
|
id: number;
|
||||||
|
text?: string | null;
|
||||||
|
result?: string | null;
|
||||||
|
}) {
|
||||||
|
return task.text?.trim() || task.result?.trim() || `Задача #${task.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTaskTransfer(item: Task | TaskTransfer): item is TaskTransfer {
|
||||||
|
return "employee_from" in item;
|
||||||
|
}
|
||||||
|
|
||||||
|
function itemTitle(item: Task | TaskTransfer) {
|
||||||
|
return isTaskTransfer(item) ? taskTitle(item.task) : taskTitle(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
function itemLabel(item: Task | TaskTransfer) {
|
||||||
|
if (isTaskTransfer(item)) {
|
||||||
|
return `Из ${personName(item.employee_from)} в ${personName(item.employee_to)} · Срок: ${item.task.deadline} · Создан: ${item.date_create}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `ID: ${item.id} · Исполнитель: ${personName(item.doer)} · Ответственный: ${personName(item.responsible)} · Срок: ${item.deadline}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function itemStatus(item: Task | TaskTransfer) {
|
||||||
|
return isTaskTransfer(item) ? item.status : item.get_status || item.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function itemPath(item: Task | TaskTransfer) {
|
||||||
|
return isTaskTransfer(item) ? `/tasks/${item.task.id}` : `/tasks/${item.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function taskParams(): TaskListParams | TaskTransferListParams | null {
|
||||||
|
if (!employee.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const employeeId = employee.value.id;
|
||||||
|
|
||||||
|
switch (mainTab.value) {
|
||||||
|
case "incoming":
|
||||||
|
return {
|
||||||
|
doer: employeeId,
|
||||||
|
archive: false,
|
||||||
|
typ: docTab.value === "all" ? undefined : docTab.value,
|
||||||
|
q: "entry",
|
||||||
|
incomplete: true,
|
||||||
|
};
|
||||||
|
case "review":
|
||||||
|
return {
|
||||||
|
q: "to_approve",
|
||||||
|
incomplete: true,
|
||||||
|
typ: docTab.value === "all" ? undefined : docTab.value,
|
||||||
|
};
|
||||||
|
case "outgoing":
|
||||||
|
return {
|
||||||
|
q: "outgoing",
|
||||||
|
archive: false,
|
||||||
|
incomplete: true,
|
||||||
|
typ: docTab.value === "all" ? undefined : docTab.value,
|
||||||
|
};
|
||||||
|
case "archive":
|
||||||
|
return {
|
||||||
|
typ: docTab.value === "all" ? undefined : docTab.value,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCurrentEmployee() {
|
||||||
|
employeeLoading.value = true;
|
||||||
|
employeeError.value = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
employee.value = await invoke<CurrentEmployee>("current_employee");
|
||||||
|
} catch (error) {
|
||||||
|
employee.value = null;
|
||||||
|
employeeError.value =
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Не удалось получить текущего сотрудника";
|
||||||
|
} finally {
|
||||||
|
employeeLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reloadList() {
|
||||||
|
if (!employee.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = taskParams();
|
||||||
|
if (!params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainTab.value === "transfer") {
|
||||||
|
await loadTaskTransfers({ employee_to: employee.value.id, status: "A" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadTasks(params as TaskListParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reloadForTabChange() {
|
||||||
|
switchingList.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await reloadList();
|
||||||
|
} finally {
|
||||||
|
switchingList.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onRefresh() {
|
||||||
|
refreshing.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await reloadList();
|
||||||
|
} finally {
|
||||||
|
refreshing.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(mainTab, () => {
|
||||||
|
if (mainTab.value === "transfer") {
|
||||||
|
suppressDocTabReload = true;
|
||||||
|
docTab.value = "all";
|
||||||
|
void nextTick(() => {
|
||||||
|
suppressDocTabReload = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void reloadForTabChange();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(docTab, () => {
|
||||||
|
if (showDocTabs.value && !suppressDocTabReload) {
|
||||||
|
void reloadForTabChange();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadCurrentEmployee();
|
||||||
|
await reloadList();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<van-notice-bar
|
<van-notice-bar
|
||||||
v-if="error"
|
v-if="employeeError || activeError"
|
||||||
class="notice"
|
class="notice"
|
||||||
color="#991b1b"
|
color="#991b1b"
|
||||||
background="#fee2e2"
|
background="#fee2e2"
|
||||||
left-icon="warning-o"
|
left-icon="warning-o"
|
||||||
wrapable
|
wrapable
|
||||||
:scrollable="false"
|
:scrollable="false"
|
||||||
:text="error"
|
:text="employeeError || activeError"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section class="card list-card">
|
<section class="card list-card">
|
||||||
<div class="list-title">
|
<van-tabbar
|
||||||
<h2>Список</h2>
|
v-model="mainTab"
|
||||||
<div class="list-actions">
|
:fixed="false"
|
||||||
<van-button size="small" plain type="primary" :loading="loading" @click="loadTasks()">
|
:border="false"
|
||||||
Обновить
|
class="task-tabbar"
|
||||||
</van-button>
|
>
|
||||||
<van-button size="small" round type="primary" icon="plus" @click="router.push('/tasks/new')" />
|
<van-tabbar-item
|
||||||
</div>
|
v-for="tab in mainTabs"
|
||||||
</div>
|
:key="tab.key"
|
||||||
|
:name="tab.key"
|
||||||
<van-loading v-if="loading && tasks.length === 0" class="state" type="spinner">
|
:icon="tab.icon"
|
||||||
Загрузка...
|
|
||||||
</van-loading>
|
|
||||||
|
|
||||||
<van-empty v-else-if="tasks.length === 0" description="Задач пока нет" />
|
|
||||||
|
|
||||||
<van-cell-group v-else inset>
|
|
||||||
<van-cell
|
|
||||||
v-for="task in tasks"
|
|
||||||
:key="task.id"
|
|
||||||
:title="task.name"
|
|
||||||
center
|
|
||||||
is-link
|
|
||||||
@click="router.push(`/tasks/${task.id}`)"
|
|
||||||
>
|
>
|
||||||
<template #label>
|
{{ tab.label }}
|
||||||
ID: {{ task.id }} · Автор: {{ employeeName(task.author_id) }} · Ответственный:
|
</van-tabbar-item>
|
||||||
{{ employeeName(task.responsible_id) }}
|
</van-tabbar>
|
||||||
</template>
|
|
||||||
<template #right-icon>
|
<van-tabs
|
||||||
<van-tag plain type="primary">Task</van-tag>
|
v-if="showDocTabs"
|
||||||
</template>
|
v-model:active="docTab"
|
||||||
</van-cell>
|
shrink
|
||||||
</van-cell-group>
|
class="tabs tabs-secondary"
|
||||||
|
>
|
||||||
|
<van-tab
|
||||||
|
v-for="tab in docTabs"
|
||||||
|
:key="tab.key"
|
||||||
|
:name="tab.key"
|
||||||
|
:title="tab.label"
|
||||||
|
/>
|
||||||
|
</van-tabs>
|
||||||
|
|
||||||
|
<van-pull-refresh
|
||||||
|
v-if="!switchingList"
|
||||||
|
v-model="refreshing"
|
||||||
|
class="pull-refresh"
|
||||||
|
@refresh="onRefresh"
|
||||||
|
>
|
||||||
|
<van-list
|
||||||
|
:loading="activeLoading || refreshing"
|
||||||
|
:finished="true"
|
||||||
|
loading-text="Загрузка..."
|
||||||
|
:immediate-check="false"
|
||||||
|
>
|
||||||
|
<van-loading v-if="showEmployeeLoading" class="state" type="spinner">
|
||||||
|
Загрузка...
|
||||||
|
</van-loading>
|
||||||
|
|
||||||
|
<van-empty
|
||||||
|
v-else-if="visibleItems.length === 0"
|
||||||
|
description="Записей пока нет"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<van-cell-group v-else inset>
|
||||||
|
<van-cell
|
||||||
|
v-for="item in visibleItems"
|
||||||
|
:key="item.id"
|
||||||
|
:title="itemTitle(item)"
|
||||||
|
center
|
||||||
|
is-link
|
||||||
|
@click="router.push(itemPath(item))"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
{{ itemLabel(item) }}
|
||||||
|
</template>
|
||||||
|
<template #right-icon>
|
||||||
|
<van-tag plain type="primary">{{ itemStatus(item) }}</van-tag>
|
||||||
|
</template>
|
||||||
|
</van-cell>
|
||||||
|
</van-cell-group>
|
||||||
|
</van-list>
|
||||||
|
</van-pull-refresh>
|
||||||
|
|
||||||
|
<van-loading v-else class="state" type="spinner">Загрузка...</van-loading>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<van-floating-bubble icon="plus" @click="router.push('/tasks/new')" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.list-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-tabbar {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
|
margin: -12px -12px 8px;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-refresh {
|
||||||
|
min-height: calc(100vh - 220px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.pull-refresh {
|
||||||
|
min-height: calc(100vh - 200px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import type {
|
|||||||
TaskCreate,
|
TaskCreate,
|
||||||
TaskUpdate,
|
TaskUpdate,
|
||||||
TaskListParams,
|
TaskListParams,
|
||||||
|
TaskTransfer,
|
||||||
|
TaskTransferListParams,
|
||||||
User,
|
User,
|
||||||
UserCreate,
|
UserCreate,
|
||||||
UserUpdate,
|
UserUpdate,
|
||||||
@@ -51,4 +53,5 @@ export const frcApi = createModelApi<Frc, FrcCreate, FrcUpdate, FrcListParams>("
|
|||||||
export const messageApi = createModelApi<Message, MessageCreate, MessageUpdate, MessageListParams>("message");
|
export const messageApi = createModelApi<Message, MessageCreate, MessageUpdate, MessageListParams>("message");
|
||||||
export const projectApi = createModelApi<Project, ProjectCreate, ProjectUpdate, ProjectListParams>("project");
|
export const projectApi = createModelApi<Project, ProjectCreate, ProjectUpdate, ProjectListParams>("project");
|
||||||
export const taskApi = createModelApi<Task, TaskCreate, TaskUpdate, TaskListParams>("task");
|
export const taskApi = createModelApi<Task, TaskCreate, TaskUpdate, TaskListParams>("task");
|
||||||
|
export const taskTransferApi = createModelApi<TaskTransfer, Partial<TaskTransfer>, Partial<TaskTransfer>, TaskTransferListParams>("task_transfer");
|
||||||
export const userApi = createModelApi<User, UserCreate, UserUpdate, UserListParams>("users");
|
export const userApi = createModelApi<User, UserCreate, UserUpdate, UserListParams>("users");
|
||||||
|
|||||||
+241
-14
@@ -319,6 +319,107 @@ export interface MessageListParams extends ListParams {
|
|||||||
employee_id?: number;
|
employee_id?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TaskTransferPerson {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
short_name: string;
|
||||||
|
first_position: string;
|
||||||
|
get_main_position: string;
|
||||||
|
departament_name: string;
|
||||||
|
company: Record<string, unknown> | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskTransferProject {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
short_name: string;
|
||||||
|
tender: string;
|
||||||
|
get_absolute_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskTransferTask {
|
||||||
|
id: number;
|
||||||
|
text: string;
|
||||||
|
author: TaskTransferPerson;
|
||||||
|
doer: TaskTransferPerson;
|
||||||
|
deadline: string;
|
||||||
|
get_last_day: string;
|
||||||
|
status: string;
|
||||||
|
project: TaskTransferProject | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskTransfer {
|
||||||
|
id: number;
|
||||||
|
employee_from: TaskTransferPerson;
|
||||||
|
employee_to: TaskTransferPerson;
|
||||||
|
date_create: string;
|
||||||
|
task: TaskTransferTask;
|
||||||
|
status: string;
|
||||||
|
typ: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskTransferListParams extends ListParams {
|
||||||
|
id?: number;
|
||||||
|
employee_from?: number;
|
||||||
|
employee_to?: number;
|
||||||
|
status?: string;
|
||||||
|
typ?: string;
|
||||||
|
date_create?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskPosition {
|
||||||
|
id: number;
|
||||||
|
frc_name: string;
|
||||||
|
name: string;
|
||||||
|
frc: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskPerson {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
short_name: string;
|
||||||
|
avatar: string | null;
|
||||||
|
avatar_small: string | null;
|
||||||
|
first_position: string;
|
||||||
|
company: Record<string, unknown> | null;
|
||||||
|
position: TaskPosition[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskPersonInput {
|
||||||
|
id?: number;
|
||||||
|
name: string;
|
||||||
|
short_name: string;
|
||||||
|
avatar?: string | null;
|
||||||
|
avatar_small?: string | null;
|
||||||
|
first_position?: string;
|
||||||
|
company?: Record<string, unknown> | null;
|
||||||
|
position?: TaskPosition[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskProject {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
short_name: string;
|
||||||
|
tender: string;
|
||||||
|
get_absolute_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskMessage {
|
||||||
|
id: number;
|
||||||
|
author: TaskPerson;
|
||||||
|
text: string;
|
||||||
|
request_new_deadline: string | null;
|
||||||
|
recipient: TaskPerson;
|
||||||
|
date: string;
|
||||||
|
status: string;
|
||||||
|
task: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskRelatedObject {
|
||||||
|
id: number;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Project {
|
export interface Project {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -354,29 +455,156 @@ export interface ProjectListParams extends ListParams {
|
|||||||
|
|
||||||
export interface Task {
|
export interface Task {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
project: TaskProject | null;
|
||||||
author_id: number | null;
|
doer: TaskPerson;
|
||||||
responsible_id: number | null;
|
doer_name: string;
|
||||||
|
author: TaskPerson | null;
|
||||||
|
memo_full: TaskRelatedObject | null;
|
||||||
|
bill_full: TaskRelatedObject | null;
|
||||||
|
contract_full: TaskRelatedObject | null;
|
||||||
|
contract_application_full: TaskRelatedObject | null;
|
||||||
|
outgoing_letter_full: TaskRelatedObject | null;
|
||||||
|
entry_letter_full: TaskRelatedObject | null;
|
||||||
|
protocolitem_full: TaskRelatedObject | null;
|
||||||
|
decree_full: TaskRelatedObject | null;
|
||||||
|
delivery_full: TaskRelatedObject | null;
|
||||||
|
get_status: string;
|
||||||
|
get_status_class: string;
|
||||||
|
get_scan_url: string | null;
|
||||||
|
get_last_day: string;
|
||||||
|
frc_icon: string;
|
||||||
|
uploadfile_set: unknown[];
|
||||||
|
deadline: string;
|
||||||
|
request_new_deadline: string | null;
|
||||||
|
plan_date: string | null;
|
||||||
|
date: string;
|
||||||
|
message_set: TaskMessage[];
|
||||||
|
responsible: TaskPerson | null;
|
||||||
|
counterparty: TaskRelatedObject | null;
|
||||||
|
get_deadline_history: unknown[];
|
||||||
|
duration: number | null;
|
||||||
|
bid_full: TaskRelatedObject | null;
|
||||||
|
price_agreement_full: TaskRelatedObject | null;
|
||||||
|
transfer: unknown;
|
||||||
|
text: string;
|
||||||
|
status: string;
|
||||||
|
result: string;
|
||||||
|
complit_date: string | null;
|
||||||
|
comment: string;
|
||||||
|
note: string;
|
||||||
|
approve: boolean;
|
||||||
|
archive: boolean;
|
||||||
|
approve_date: string | null;
|
||||||
|
approve_required: boolean;
|
||||||
|
progress_status: string;
|
||||||
|
start_date: string | null;
|
||||||
|
end_date: string | null;
|
||||||
|
order_number: number | null;
|
||||||
|
priority: number | null;
|
||||||
|
typ: string;
|
||||||
|
stage: unknown;
|
||||||
|
contract: unknown;
|
||||||
|
questionnair: unknown;
|
||||||
|
contract_application: unknown;
|
||||||
|
entry_letter: unknown;
|
||||||
|
outgoing_letter: unknown;
|
||||||
|
protocol: unknown;
|
||||||
|
bill: unknown;
|
||||||
|
decree: unknown;
|
||||||
|
court_case: unknown;
|
||||||
|
bill_register: unknown;
|
||||||
|
price_agreement: unknown;
|
||||||
|
bid: unknown;
|
||||||
|
delivery: unknown;
|
||||||
|
scheduled_task: unknown;
|
||||||
|
report: unknown;
|
||||||
|
memo: unknown;
|
||||||
|
protocolitem: unknown;
|
||||||
|
related_note: unknown;
|
||||||
|
task: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TaskCreate {
|
export interface TaskCreate {
|
||||||
name: string;
|
project?: TaskProject | null;
|
||||||
author_id?: number | null;
|
doer: TaskPersonInput;
|
||||||
responsible_id?: number | null;
|
get_status?: string;
|
||||||
|
get_status_class?: string;
|
||||||
|
get_scan_url?: string | null;
|
||||||
|
get_last_day?: string;
|
||||||
|
frc_icon?: string;
|
||||||
|
deadline: string;
|
||||||
|
request_new_deadline?: string | null;
|
||||||
|
plan_date?: string | null;
|
||||||
|
responsible?: TaskPersonInput | null;
|
||||||
|
counterparty?: TaskRelatedObject | null;
|
||||||
|
bid_full?: TaskRelatedObject | null;
|
||||||
|
price_agreement_full?: TaskRelatedObject | null;
|
||||||
|
text?: string;
|
||||||
|
status?: string;
|
||||||
|
result?: string;
|
||||||
|
complit_date?: string | null;
|
||||||
|
comment?: string;
|
||||||
|
note?: string;
|
||||||
|
approve?: boolean;
|
||||||
|
archive?: boolean;
|
||||||
|
approve_date?: string | null;
|
||||||
|
approve_required?: boolean;
|
||||||
|
progress_status?: string;
|
||||||
|
start_date?: string | null;
|
||||||
|
end_date?: string | null;
|
||||||
|
order_number?: number | null;
|
||||||
|
priority?: number | null;
|
||||||
|
typ?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TaskUpdate {
|
export interface TaskUpdate {
|
||||||
name?: string;
|
project?: TaskProject | null;
|
||||||
author_id?: number | null;
|
doer?: TaskPersonInput;
|
||||||
responsible_id?: number | null;
|
get_status?: string;
|
||||||
|
get_status_class?: string;
|
||||||
|
get_scan_url?: string | null;
|
||||||
|
get_last_day?: string;
|
||||||
|
frc_icon?: string;
|
||||||
|
deadline?: string;
|
||||||
|
request_new_deadline?: string | null;
|
||||||
|
plan_date?: string | null;
|
||||||
|
responsible?: TaskPersonInput | null;
|
||||||
|
counterparty?: TaskRelatedObject | null;
|
||||||
|
bid_full?: TaskRelatedObject | null;
|
||||||
|
price_agreement_full?: TaskRelatedObject | null;
|
||||||
|
text?: string;
|
||||||
|
status?: string;
|
||||||
|
result?: string;
|
||||||
|
complit_date?: string | null;
|
||||||
|
comment?: string;
|
||||||
|
note?: string;
|
||||||
|
approve?: boolean;
|
||||||
|
archive?: boolean;
|
||||||
|
approve_date?: string | null;
|
||||||
|
approve_required?: boolean;
|
||||||
|
progress_status?: string;
|
||||||
|
start_date?: string | null;
|
||||||
|
end_date?: string | null;
|
||||||
|
order_number?: number | null;
|
||||||
|
priority?: number | null;
|
||||||
|
typ?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TaskListParams extends ListParams {
|
export interface TaskListParams extends ListParams {
|
||||||
id?: number;
|
id?: number;
|
||||||
name?: string;
|
text?: string;
|
||||||
name__contains?: string;
|
text__contains?: string;
|
||||||
author_id?: number | null;
|
doer?: number | null;
|
||||||
responsible_id?: number | null;
|
author?: number | null;
|
||||||
|
archive?: boolean;
|
||||||
|
status?: string;
|
||||||
|
result?: string;
|
||||||
|
doer_name?: string;
|
||||||
|
doer_name__contains?: string;
|
||||||
|
deadline?: string;
|
||||||
|
progress_status?: string;
|
||||||
|
priority?: number | null;
|
||||||
|
typ?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
@@ -397,4 +625,3 @@ export interface UserListParams extends ListParams {
|
|||||||
name?: string;
|
name?: string;
|
||||||
name__contains?: string;
|
name__contains?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user