3001-фух.это был сильный спринт.

сервис стал сильно лучше
черновики сохраняются одним запросом
дто черновиков вынесен отдельно
This commit is contained in:
2026-01-30 02:24:26 +03:00
parent 4da5fdd130
commit 10882f55c8
8 changed files with 306 additions and 51 deletions

View File

@@ -34,6 +34,7 @@ import ExcelPreviewModal from "../common/ExcelPreviewModal";
import { useActiveDraftStore } from "../../stores/activeDraftStore";
import type {
DraftItem,
UpdateDraftRequest,
CommitDraftRequest,
ReorderDraftItemsRequest,
} from "../../services/types";
@@ -65,6 +66,7 @@ export const DraftEditor: React.FC<DraftEditorProps> = ({
addItem,
reorderItems,
resetDirty,
markAsDirty,
} = useActiveDraftStore();
// Отслеживаем текущий draftId для инициализации стора
@@ -168,44 +170,29 @@ export const DraftEditor: React.FC<DraftEditorProps> = ({
// --- ЭФФЕКТЫ ---
// Инициализация стора при загрузке черновика
// Инициализация данных при загрузке черновика
useEffect(() => {
if (draft && draft.items) {
// Инициализируем стор только если изменился draftId или стор пуст
if (draft) {
// Инициализируем только если изменился draftId или стор пуст
if (currentDraftIdRef.current !== draft.id || items.length === 0) {
setItems(draft.items);
// 1. Инициализация строк (Store)
setItems(draft.items || []);
// 2. Инициализация шапки (Form)
form.setFieldsValue({
store_id: draft.store_id,
supplier_id: draft.supplier_id,
comment: draft.comment,
incoming_document_number: draft.incoming_document_number,
date_incoming: draft.date_incoming
? dayjs(draft.date_incoming)
: dayjs(),
});
currentDraftIdRef.current = draft.id;
}
}
}, [draft, items.length, setItems]);
useEffect(() => {
if (draft) {
const currentValues = form.getFieldsValue();
if (!currentValues.store_id && draft.store_id)
form.setFieldValue("store_id", draft.store_id);
if (!currentValues.supplier_id && draft.supplier_id)
form.setFieldValue("supplier_id", draft.supplier_id);
if (!currentValues.comment && draft.comment)
form.setFieldValue("comment", draft.comment);
// Инициализация входящего номера
if (
!currentValues.incoming_document_number &&
draft.incoming_document_number
)
form.setFieldValue(
"incoming_document_number",
draft.incoming_document_number
);
if (!currentValues.date_incoming)
form.setFieldValue(
"date_incoming",
draft.date_incoming ? dayjs(draft.date_incoming) : dayjs()
);
}
}, [draft, form]);
}, [draft, items.length, setItems, form]);
// --- ХЕЛПЕРЫ ---
const totalSum = useMemo(() => {
@@ -230,8 +217,8 @@ export const DraftEditor: React.FC<DraftEditorProps> = ({
// Собираем значения формы для обновления шапки черновика
const formValues = form.getFieldsValue();
// Подготавливаем payload для обновления мета-данных черновика
const draftPayload: Partial<CommitDraftRequest> = {
// Формируем единый payload для пакетного обновления (шапка + элементы)
const payload: UpdateDraftRequest = {
store_id: formValues.store_id,
supplier_id: formValues.supplier_id,
comment: formValues.comment || "",
@@ -239,24 +226,18 @@ export const DraftEditor: React.FC<DraftEditorProps> = ({
date_incoming: formValues.date_incoming
? formValues.date_incoming.format("YYYY-MM-DD")
: undefined,
};
// Сохраняем все измененные элементы
const savePromises = items.map((item) =>
api.updateDraftItem(draftId, item.id, {
product_id: item.product_id ?? null,
container_id: item.container_id ?? null,
items: items.map((item) => ({
id: item.id,
product_id: item.product_id ?? "",
container_id: item.container_id ?? "",
quantity: Number(item.quantity),
price: Number(item.price),
sum: Number(item.sum),
})
);
})),
};
// Параллельно сохраняем шапку и строки
await Promise.all([
api.updateDraft(draftId, draftPayload),
...savePromises,
]);
// Отправляем единый запрос на сервер
await api.updateDraft(draftId, payload);
// После успешного сохранения обновляем данные с сервера
await queryClient.invalidateQueries({ queryKey: ["draft", draftId] });
@@ -540,7 +521,7 @@ export const DraftEditor: React.FC<DraftEditorProps> = ({
<Form
form={form}
layout="vertical"
initialValues={{ date_incoming: dayjs() }}
onValuesChange={() => markAsDirty()}
>
<Row gutter={[8, 8]}>
<Col span={12}>

View File

@@ -18,6 +18,7 @@ import type {
DraftInvoice,
DraftItem,
UpdateDraftItemRequest,
UpdateDraftRequest,
CommitDraftRequest,
ReorderDraftItemsRequest,
ProductSearchResult,
@@ -213,7 +214,7 @@ export const api = {
return data;
},
updateDraft: async (id: string, payload: Partial<CommitDraftRequest>): Promise<DraftInvoice> => {
updateDraft: async (id: string, payload: UpdateDraftRequest): Promise<DraftInvoice> => {
const { data } = await apiClient.patch<DraftInvoice>(`/drafts/${id}`, payload);
return data;
},

View File

@@ -229,6 +229,7 @@ export interface DraftInvoice {
// DTO для обновления строки
export interface UpdateDraftItemRequest {
id?: UUID; // ID элемента для идентификации при пакетном обновлении
product_id?: UUID | null;
container_id?: UUID | null;
quantity?: number;
@@ -237,6 +238,16 @@ export interface UpdateDraftItemRequest {
edited_field?: string; // ('quantity' | 'price' | 'sum')
}
// DTO для пакетного обновления черновика (шапка + элементы)
export interface UpdateDraftRequest {
date_incoming?: string;
store_id?: UUID;
supplier_id?: UUID;
comment?: string;
incoming_document_number?: string;
items?: UpdateDraftItemRequest[];
}
// DTO для коммита
export interface CommitDraftRequest {
date_incoming: string;

View File

@@ -65,6 +65,12 @@ interface ActiveDraftStore {
* Используется после успешного сохранения изменений на сервер
*/
resetDirty: () => void;
/**
* Устанавливает флаг isDirty в true
* Используется для пометки черновика как измененного
*/
markAsDirty: () => void;
}
/**
@@ -181,5 +187,14 @@ export const useActiveDraftStore = create<ActiveDraftStore>()(
set((state) => {
state.isDirty = false;
}),
/**
* Устанавливает флаг isDirty в true
* Используется для пометки черновика как измененного
*/
markAsDirty: () =>
set((state) => {
state.isDirty = true;
}),
}))
);