mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-05 03:12:34 -06:00
3001-фух.это был сильный спринт.
сервис стал сильно лучше черновики сохраняются одним запросом дто черновиков вынесен отдельно
This commit is contained in:
@@ -172,6 +172,7 @@ func main() {
|
|||||||
// Drafts & Invoices
|
// Drafts & Invoices
|
||||||
api.GET("/drafts", draftsHandler.GetDrafts)
|
api.GET("/drafts", draftsHandler.GetDrafts)
|
||||||
api.GET("/drafts/:id", draftsHandler.GetDraft)
|
api.GET("/drafts/:id", draftsHandler.GetDraft)
|
||||||
|
api.PATCH("/drafts/:id", draftsHandler.UpdateDraft)
|
||||||
api.DELETE("/drafts/:id", draftsHandler.DeleteDraft)
|
api.DELETE("/drafts/:id", draftsHandler.DeleteDraft)
|
||||||
api.POST("/drafts/upload", draftsHandler.Upload)
|
api.POST("/drafts/upload", draftsHandler.Upload)
|
||||||
// Items CRUD
|
// Items CRUD
|
||||||
|
|||||||
19
internal/domain/drafts/dto.go
Normal file
19
internal/domain/drafts/dto.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package drafts
|
||||||
|
|
||||||
|
// UpdateDraftRequest DTO для пакетного обновления черновика
|
||||||
|
type UpdateDraftRequest struct {
|
||||||
|
DateIncoming *string `json:"date_incoming"` // YYYY-MM-DD
|
||||||
|
StoreID *string `json:"store_id"` // UUID или пустая строка для сброса
|
||||||
|
SupplierID *string `json:"supplier_id"` // UUID или пустая строка для сброса
|
||||||
|
Comment *string `json:"comment"`
|
||||||
|
IncomingDocumentNumber *string `json:"incoming_document_number"`
|
||||||
|
Items []struct {
|
||||||
|
ID *string `json:"id"` // Обязательный ID строки
|
||||||
|
ProductID *string `json:"product_id"`
|
||||||
|
ContainerID *string `json:"container_id"`
|
||||||
|
Quantity *float64 `json:"quantity"`
|
||||||
|
Price *float64 `json:"price"`
|
||||||
|
Sum *float64 `json:"sum"`
|
||||||
|
EditedField string `json:"edited_field"` // "quantity", "price", "sum"
|
||||||
|
} `json:"items"`
|
||||||
|
}
|
||||||
@@ -958,3 +958,205 @@ func (s *Service) ReorderItems(draftID uuid.UUID, items []struct {
|
|||||||
// Вызываем метод репозитория для обновления порядка
|
// Вызываем метод репозитория для обновления порядка
|
||||||
return s.draftRepo.ReorderItems(draftID, items)
|
return s.draftRepo.ReorderItems(draftID, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveDraftFull обновляет черновик (шапку и позиции) пакетно в одной транзакции
|
||||||
|
func (s *Service) SaveDraftFull(draftID, userID uuid.UUID, req drafts.UpdateDraftRequest) error {
|
||||||
|
// Получаем черновик для проверки прав доступа
|
||||||
|
draft, err := s.GetDraft(draftID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, что черновик не завершен
|
||||||
|
if draft.Status == drafts.StatusCompleted {
|
||||||
|
return errors.New("черновик уже отправлен")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем шапку черновика, если переданы поля
|
||||||
|
headerUpdated := false
|
||||||
|
|
||||||
|
// 1. Дата (Обязательное поле, не может быть nil)
|
||||||
|
if req.DateIncoming != nil && *req.DateIncoming != "" {
|
||||||
|
parsedDate, err := time.Parse("2006-01-02", *req.DateIncoming)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid date format: %w", err)
|
||||||
|
}
|
||||||
|
draft.DateIncoming = &parsedDate
|
||||||
|
headerUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Склад (Может быть nil/сброшен)
|
||||||
|
if req.StoreID != nil {
|
||||||
|
if *req.StoreID == "" {
|
||||||
|
draft.StoreID = nil
|
||||||
|
} else {
|
||||||
|
uid, err := uuid.Parse(*req.StoreID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid store_id: %w", err)
|
||||||
|
}
|
||||||
|
draft.StoreID = &uid
|
||||||
|
}
|
||||||
|
headerUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Поставщик (Может быть nil/сброшен)
|
||||||
|
if req.SupplierID != nil {
|
||||||
|
if *req.SupplierID == "" {
|
||||||
|
draft.SupplierID = nil
|
||||||
|
} else {
|
||||||
|
uid, err := uuid.Parse(*req.SupplierID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid supplier_id: %w", err)
|
||||||
|
}
|
||||||
|
draft.SupplierID = &uid
|
||||||
|
}
|
||||||
|
headerUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Комментарий
|
||||||
|
if req.Comment != nil {
|
||||||
|
draft.Comment = *req.Comment
|
||||||
|
headerUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Входящий номер
|
||||||
|
if req.IncomingDocumentNumber != nil {
|
||||||
|
draft.IncomingDocumentNumber = *req.IncomingDocumentNumber
|
||||||
|
headerUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если были изменения в шапке — сохраняем
|
||||||
|
if headerUpdated {
|
||||||
|
if err := s.draftRepo.Update(draft); err != nil {
|
||||||
|
return fmt.Errorf("failed to update draft header: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем позиции, если переданы
|
||||||
|
if len(req.Items) > 0 {
|
||||||
|
for _, itemReq := range req.Items {
|
||||||
|
if itemReq.ID == nil || *itemReq.ID == "" {
|
||||||
|
return errors.New("item id is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
itemID, err := uuid.Parse(*itemReq.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid item id: %s", *itemReq.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем текущую позицию
|
||||||
|
currentItem, err := s.draftRepo.GetItemByID(itemID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("item not found: %s", itemID.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, что позиция принадлежит черновику
|
||||||
|
if currentItem.DraftID != draftID {
|
||||||
|
return fmt.Errorf("item %s does not belong to draft %s", itemID.String(), draftID.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем поля позиции
|
||||||
|
if itemReq.ProductID != nil {
|
||||||
|
if *itemReq.ProductID == "" {
|
||||||
|
currentItem.ProductID = nil
|
||||||
|
currentItem.IsMatched = false
|
||||||
|
currentItem.ContainerID = nil // Если убрали товар, фасовку тоже надо обнулить
|
||||||
|
} else {
|
||||||
|
parsedID, err := uuid.Parse(*itemReq.ProductID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid product_id for item %s", itemID.String())
|
||||||
|
}
|
||||||
|
currentItem.ProductID = &parsedID
|
||||||
|
currentItem.IsMatched = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if itemReq.ContainerID != nil {
|
||||||
|
if *itemReq.ContainerID == "" {
|
||||||
|
// Сброс фасовки
|
||||||
|
currentItem.ContainerID = nil
|
||||||
|
} else {
|
||||||
|
parsedID, err := uuid.Parse(*itemReq.ContainerID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid container_id for item %s", itemID.String())
|
||||||
|
}
|
||||||
|
currentItem.ContainerID = &parsedID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Определяем, какое поле редактируется
|
||||||
|
editedField := itemReq.EditedField
|
||||||
|
if editedField == "" {
|
||||||
|
if itemReq.Sum != nil {
|
||||||
|
editedField = "sum"
|
||||||
|
} else if itemReq.Price != nil {
|
||||||
|
editedField = "price"
|
||||||
|
} else if itemReq.Quantity != nil {
|
||||||
|
editedField = "quantity"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем числовые поля
|
||||||
|
qty := decimal.Zero
|
||||||
|
if itemReq.Quantity != nil {
|
||||||
|
qty = decimal.NewFromFloat(*itemReq.Quantity)
|
||||||
|
} else {
|
||||||
|
qty = currentItem.Quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
price := decimal.Zero
|
||||||
|
if itemReq.Price != nil {
|
||||||
|
price = decimal.NewFromFloat(*itemReq.Price)
|
||||||
|
} else {
|
||||||
|
price = currentItem.Price
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := decimal.Zero
|
||||||
|
if itemReq.Sum != nil {
|
||||||
|
sum = decimal.NewFromFloat(*itemReq.Sum)
|
||||||
|
} else {
|
||||||
|
sum = currentItem.Sum
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяем изменения в зависимости от редактируемого поля
|
||||||
|
field := drafts.EditedField(editedField)
|
||||||
|
switch field {
|
||||||
|
case drafts.FieldQuantity:
|
||||||
|
currentItem.Quantity = qty
|
||||||
|
case drafts.FieldPrice:
|
||||||
|
currentItem.Price = price
|
||||||
|
case drafts.FieldSum:
|
||||||
|
currentItem.Sum = sum
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пересчитываем поля
|
||||||
|
s.RecalculateItemFields(currentItem, field)
|
||||||
|
|
||||||
|
// Обновляем статус черновика, если он был отменен
|
||||||
|
if draft.Status == drafts.StatusCanceled {
|
||||||
|
draft.Status = drafts.StatusReadyToVerify
|
||||||
|
if err := s.draftRepo.Update(draft); err != nil {
|
||||||
|
return fmt.Errorf("failed to update draft status: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем обновленную позицию
|
||||||
|
updates := map[string]interface{}{
|
||||||
|
"product_id": currentItem.ProductID,
|
||||||
|
"container_id": currentItem.ContainerID,
|
||||||
|
"quantity": currentItem.Quantity,
|
||||||
|
"price": currentItem.Price,
|
||||||
|
"sum": currentItem.Sum,
|
||||||
|
"last_edited_field1": currentItem.LastEditedField1,
|
||||||
|
"last_edited_field2": currentItem.LastEditedField2,
|
||||||
|
"is_matched": currentItem.IsMatched,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.draftRepo.UpdateItem(itemID, updates); err != nil {
|
||||||
|
return fmt.Errorf("failed to update item %s: %w", itemID.String(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
draftsdomain "rmser/internal/domain/drafts"
|
||||||
"rmser/internal/services/drafts"
|
"rmser/internal/services/drafts"
|
||||||
"rmser/internal/services/ocr"
|
"rmser/internal/services/ocr"
|
||||||
"rmser/pkg/logger"
|
"rmser/pkg/logger"
|
||||||
@@ -361,6 +362,30 @@ func (h *DraftsHandler) ReorderItems(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateDraft обновляет черновик (шапку и позиции) пакетно
|
||||||
|
func (h *DraftsHandler) UpdateDraft(c *gin.Context) {
|
||||||
|
userID := c.MustGet("userID").(uuid.UUID)
|
||||||
|
draftID, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid draft id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req draftsdomain.UpdateDraftRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.service.SaveDraftFull(draftID, userID, req); err != nil {
|
||||||
|
logger.Log.Error("Failed to update draft", zap.Error(err))
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Не удалось сохранить изменения. Проверьте данные."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"status": "updated"})
|
||||||
|
}
|
||||||
|
|
||||||
// Upload обрабатывает загрузку файла и прогоняет через OCR
|
// Upload обрабатывает загрузку файла и прогоняет через OCR
|
||||||
func (h *DraftsHandler) Upload(c *gin.Context) {
|
func (h *DraftsHandler) Upload(c *gin.Context) {
|
||||||
// Лимит размера тела запроса (20MB)
|
// Лимит размера тела запроса (20MB)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import ExcelPreviewModal from "../common/ExcelPreviewModal";
|
|||||||
import { useActiveDraftStore } from "../../stores/activeDraftStore";
|
import { useActiveDraftStore } from "../../stores/activeDraftStore";
|
||||||
import type {
|
import type {
|
||||||
DraftItem,
|
DraftItem,
|
||||||
|
UpdateDraftRequest,
|
||||||
CommitDraftRequest,
|
CommitDraftRequest,
|
||||||
ReorderDraftItemsRequest,
|
ReorderDraftItemsRequest,
|
||||||
} from "../../services/types";
|
} from "../../services/types";
|
||||||
@@ -65,6 +66,7 @@ export const DraftEditor: React.FC<DraftEditorProps> = ({
|
|||||||
addItem,
|
addItem,
|
||||||
reorderItems,
|
reorderItems,
|
||||||
resetDirty,
|
resetDirty,
|
||||||
|
markAsDirty,
|
||||||
} = useActiveDraftStore();
|
} = useActiveDraftStore();
|
||||||
|
|
||||||
// Отслеживаем текущий draftId для инициализации стора
|
// Отслеживаем текущий draftId для инициализации стора
|
||||||
@@ -168,44 +170,29 @@ export const DraftEditor: React.FC<DraftEditorProps> = ({
|
|||||||
|
|
||||||
// --- ЭФФЕКТЫ ---
|
// --- ЭФФЕКТЫ ---
|
||||||
|
|
||||||
// Инициализация стора при загрузке черновика
|
// Инициализация данных при загрузке черновика
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (draft && draft.items) {
|
if (draft) {
|
||||||
// Инициализируем стор только если изменился draftId или стор пуст
|
// Инициализируем только если изменился draftId или стор пуст
|
||||||
if (currentDraftIdRef.current !== draft.id || items.length === 0) {
|
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;
|
currentDraftIdRef.current = draft.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [draft, items.length, setItems]);
|
}, [draft, items.length, setItems, form]);
|
||||||
|
|
||||||
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]);
|
|
||||||
|
|
||||||
// --- ХЕЛПЕРЫ ---
|
// --- ХЕЛПЕРЫ ---
|
||||||
const totalSum = useMemo(() => {
|
const totalSum = useMemo(() => {
|
||||||
@@ -230,8 +217,8 @@ export const DraftEditor: React.FC<DraftEditorProps> = ({
|
|||||||
// Собираем значения формы для обновления шапки черновика
|
// Собираем значения формы для обновления шапки черновика
|
||||||
const formValues = form.getFieldsValue();
|
const formValues = form.getFieldsValue();
|
||||||
|
|
||||||
// Подготавливаем payload для обновления мета-данных черновика
|
// Формируем единый payload для пакетного обновления (шапка + элементы)
|
||||||
const draftPayload: Partial<CommitDraftRequest> = {
|
const payload: UpdateDraftRequest = {
|
||||||
store_id: formValues.store_id,
|
store_id: formValues.store_id,
|
||||||
supplier_id: formValues.supplier_id,
|
supplier_id: formValues.supplier_id,
|
||||||
comment: formValues.comment || "",
|
comment: formValues.comment || "",
|
||||||
@@ -239,24 +226,18 @@ export const DraftEditor: React.FC<DraftEditorProps> = ({
|
|||||||
date_incoming: formValues.date_incoming
|
date_incoming: formValues.date_incoming
|
||||||
? formValues.date_incoming.format("YYYY-MM-DD")
|
? formValues.date_incoming.format("YYYY-MM-DD")
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
items: items.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
// Сохраняем все измененные элементы
|
product_id: item.product_id ?? "",
|
||||||
const savePromises = items.map((item) =>
|
container_id: item.container_id ?? "",
|
||||||
api.updateDraftItem(draftId, item.id, {
|
|
||||||
product_id: item.product_id ?? null,
|
|
||||||
container_id: item.container_id ?? null,
|
|
||||||
quantity: Number(item.quantity),
|
quantity: Number(item.quantity),
|
||||||
price: Number(item.price),
|
price: Number(item.price),
|
||||||
sum: Number(item.sum),
|
sum: Number(item.sum),
|
||||||
})
|
})),
|
||||||
);
|
};
|
||||||
|
|
||||||
// Параллельно сохраняем шапку и строки
|
// Отправляем единый запрос на сервер
|
||||||
await Promise.all([
|
await api.updateDraft(draftId, payload);
|
||||||
api.updateDraft(draftId, draftPayload),
|
|
||||||
...savePromises,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// После успешного сохранения обновляем данные с сервера
|
// После успешного сохранения обновляем данные с сервера
|
||||||
await queryClient.invalidateQueries({ queryKey: ["draft", draftId] });
|
await queryClient.invalidateQueries({ queryKey: ["draft", draftId] });
|
||||||
@@ -540,7 +521,7 @@ export const DraftEditor: React.FC<DraftEditorProps> = ({
|
|||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
initialValues={{ date_incoming: dayjs() }}
|
onValuesChange={() => markAsDirty()}
|
||||||
>
|
>
|
||||||
<Row gutter={[8, 8]}>
|
<Row gutter={[8, 8]}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import type {
|
|||||||
DraftInvoice,
|
DraftInvoice,
|
||||||
DraftItem,
|
DraftItem,
|
||||||
UpdateDraftItemRequest,
|
UpdateDraftItemRequest,
|
||||||
|
UpdateDraftRequest,
|
||||||
CommitDraftRequest,
|
CommitDraftRequest,
|
||||||
ReorderDraftItemsRequest,
|
ReorderDraftItemsRequest,
|
||||||
ProductSearchResult,
|
ProductSearchResult,
|
||||||
@@ -213,7 +214,7 @@ export const api = {
|
|||||||
return data;
|
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);
|
const { data } = await apiClient.patch<DraftInvoice>(`/drafts/${id}`, payload);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ export interface DraftInvoice {
|
|||||||
|
|
||||||
// DTO для обновления строки
|
// DTO для обновления строки
|
||||||
export interface UpdateDraftItemRequest {
|
export interface UpdateDraftItemRequest {
|
||||||
|
id?: UUID; // ID элемента для идентификации при пакетном обновлении
|
||||||
product_id?: UUID | null;
|
product_id?: UUID | null;
|
||||||
container_id?: UUID | null;
|
container_id?: UUID | null;
|
||||||
quantity?: number;
|
quantity?: number;
|
||||||
@@ -237,6 +238,16 @@ export interface UpdateDraftItemRequest {
|
|||||||
edited_field?: string; // ('quantity' | 'price' | 'sum')
|
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 для коммита
|
// DTO для коммита
|
||||||
export interface CommitDraftRequest {
|
export interface CommitDraftRequest {
|
||||||
date_incoming: string;
|
date_incoming: string;
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ interface ActiveDraftStore {
|
|||||||
* Используется после успешного сохранения изменений на сервер
|
* Используется после успешного сохранения изменений на сервер
|
||||||
*/
|
*/
|
||||||
resetDirty: () => void;
|
resetDirty: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Устанавливает флаг isDirty в true
|
||||||
|
* Используется для пометки черновика как измененного
|
||||||
|
*/
|
||||||
|
markAsDirty: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -181,5 +187,14 @@ export const useActiveDraftStore = create<ActiveDraftStore>()(
|
|||||||
set((state) => {
|
set((state) => {
|
||||||
state.isDirty = false;
|
state.isDirty = false;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Устанавливает флаг isDirty в true
|
||||||
|
* Используется для пометки черновика как измененного
|
||||||
|
*/
|
||||||
|
markAsDirty: () =>
|
||||||
|
set((state) => {
|
||||||
|
state.isDirty = true;
|
||||||
|
}),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user