0202-финиш перед десктопом

пересчет поправил
редактирование с перепроведением
галка автопроведения работает
рекомендации починил
This commit is contained in:
2026-02-02 13:53:38 +03:00
parent 10882f55c8
commit 88620f3fb6
37 changed files with 1905 additions and 11162 deletions

View File

@@ -157,9 +157,6 @@ func (s *Service) UpdateDraftHeader(id uuid.UUID, storeID *uuid.UUID, supplierID
if err != nil {
return err
}
if draft.Status == drafts.StatusCompleted {
return errors.New("черновик уже отправлен")
}
draft.StoreID = storeID
draft.SupplierID = supplierID
draft.DateIncoming = &date
@@ -227,7 +224,7 @@ func (s *Service) DeleteItem(draftID, itemID uuid.UUID) (float64, error) {
return sumFloat, nil
}
// RecalculateItemFields - логика пересчета Qty/Price/Sum
// RecalculateItemFields - логика пересчета Q->P->S->Q (Quantity -> Price -> Sum -> Quantity) с использованием decimal для точности
func (s *Service) RecalculateItemFields(item *drafts.DraftInvoiceItem, editedField drafts.EditedField) {
if item.LastEditedField1 != editedField {
item.LastEditedField2 = item.LastEditedField1
@@ -265,6 +262,29 @@ func (s *Service) RecalculateItemFields(item *drafts.DraftInvoiceItem, editedFie
case drafts.FieldSum:
item.Sum = item.Quantity.Mul(item.Price)
}
// Дополнительная проверка для гарантии консистентности всех полей (Q->P->S->Q)
// Используется только для обеспечения точности, не влияет на логику выбора пересчитываемого поля
if !item.Price.IsZero() && !item.Quantity.IsZero() {
calculatedSum := item.Quantity.Mul(item.Price)
if !calculatedSum.Equal(item.Sum) {
item.Sum = calculatedSum
}
}
if !item.Price.IsZero() && !item.Sum.IsZero() {
calculatedQuantity := item.Sum.Div(item.Price)
if !calculatedQuantity.Equal(item.Quantity) {
item.Quantity = calculatedQuantity
}
}
if !item.Quantity.IsZero() && !item.Sum.IsZero() {
calculatedPrice := item.Sum.Div(item.Quantity)
if !calculatedPrice.Equal(item.Price) {
item.Price = calculatedPrice
}
}
}
// UpdateItem обновлен для поддержки динамического пересчета
@@ -293,17 +313,10 @@ func (s *Service) UpdateItem(draftID, itemID uuid.UUID, productID *uuid.UUID, co
}
}
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)
// Просто присваиваем значения от фронтенда без пересчета
currentItem.Quantity = qty
currentItem.Price = price
currentItem.Sum = sum
if draft.Status == drafts.StatusCanceled {
draft.Status = drafts.StatusReadyToVerify
@@ -311,20 +324,18 @@ func (s *Service) UpdateItem(draftID, itemID uuid.UUID, productID *uuid.UUID, co
}
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,
"product_id": currentItem.ProductID,
"container_id": currentItem.ContainerID,
"quantity": currentItem.Quantity,
"price": currentItem.Price,
"sum": currentItem.Sum,
"is_matched": currentItem.IsMatched,
}
return s.draftRepo.UpdateItem(itemID, updates)
}
func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
func (s *Service) CommitDraft(draftID, userID uuid.UUID, isProcessed bool) (string, error) {
server, err := s.accountRepo.GetActiveServer(userID)
if err != nil {
return "", fmt.Errorf("active server not found: %w", err)
@@ -347,17 +358,13 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
return "", errors.New("черновик принадлежит другому серверу")
}
if draft.Status == drafts.StatusCompleted {
return "", errors.New("накладная уже отправлена")
}
client, err := s.rmsFactory.GetClientForUser(userID)
if err != nil {
return "", err
}
targetStatus := "NEW"
if server.AutoProcess {
if isProcessed {
targetStatus = "PROCESSED"
}
@@ -373,6 +380,11 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
Items: make([]invoices.InvoiceItem, 0, len(draft.Items)),
}
// Если черновик уже был отправлен ранее, передаем RMSInvoiceID для обновления
if draft.RMSInvoiceID != nil {
inv.ID = *draft.RMSInvoiceID
}
for _, dItem := range draft.Items {
if dItem.ProductID == nil {
continue
@@ -405,6 +417,12 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
Price: priceToSend,
Sum: sum,
ContainerID: dItem.ContainerID,
Product: func() catalog.Product {
if dItem.Product != nil {
return *dItem.Product
}
return catalog.Product{}
}(),
}
inv.Items = append(inv.Items, invItem)
}
@@ -415,7 +433,22 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
docNum, err := client.CreateIncomingInvoice(inv)
if err != nil {
return "", err
// Если накладная уже проведена, пробуем распровести и повторить
if strings.Contains(err.Error(), "Changing processed") {
logger.Log.Info("Накладная проведена, выполняю распроведение...", zap.String("doc_num", draft.DocumentNumber))
if unprocessErr := client.UnprocessIncomingInvoice(inv); unprocessErr != nil {
return "", fmt.Errorf("не удалось распровести накладную: %w", unprocessErr)
}
// Повторяем попытку создания накладной после распроведения
docNum, err = client.CreateIncomingInvoice(inv)
if err != nil {
return "", err
}
} else {
return "", err
}
}
invoices, err := client.FetchInvoices(*draft.DateIncoming, *draft.DateIncoming)
@@ -434,6 +467,7 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
for _, invoice := range invoices {
if invoice.DocumentNumber == docNum {
draft.RMSInvoiceID = &invoice.ID
draft.DocumentNumber = invoice.DocumentNumber
found = true
break
}
@@ -555,19 +589,20 @@ func (s *Service) CreateProductContainer(userID uuid.UUID, productID uuid.UUID,
}
type UnifiedInvoiceDTO struct {
ID uuid.UUID `json:"id"`
Type string `json:"type"`
DocumentNumber string `json:"document_number"`
IncomingNumber string `json:"incoming_number"`
DateIncoming time.Time `json:"date_incoming"`
Status string `json:"status"`
TotalSum float64 `json:"total_sum"`
StoreName string `json:"store_name"`
ItemsCount int `json:"items_count"`
CreatedAt time.Time `json:"created_at"`
IsAppCreated bool `json:"is_app_created"`
PhotoURL string `json:"photo_url"`
ItemsPreview string `json:"items_preview"`
ID uuid.UUID `json:"id"`
Type string `json:"type"`
DocumentNumber string `json:"document_number"`
IncomingNumber string `json:"incoming_number"`
DateIncoming time.Time `json:"date_incoming"`
Status string `json:"status"`
TotalSum float64 `json:"total_sum"`
StoreName string `json:"store_name"`
ItemsCount int `json:"items_count"`
CreatedAt time.Time `json:"created_at"`
IsAppCreated bool `json:"is_app_created"`
PhotoURL string `json:"photo_url"`
ItemsPreview string `json:"items_preview"`
DraftID *uuid.UUID `json:"draft_id,omitempty"` // ID черновика для SYNCED накладных
}
func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]UnifiedInvoiceDTO, error) {
@@ -586,7 +621,7 @@ func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]Unifie
return nil, err
}
photoMap, err := s.draftRepo.GetRMSInvoiceIDToPhotoURLMap(server.ID)
linkedDraftsMap, err := s.draftRepo.GetLinkedDraftsMap(server.ID)
if err != nil {
return nil, err
}
@@ -647,9 +682,11 @@ func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]Unifie
isAppCreated := false
photoURL := ""
if url, exists := photoMap[inv.ID]; exists {
var draftID *uuid.UUID
if linkedInfo, exists := linkedDraftsMap[inv.ID]; exists {
isAppCreated = true
photoURL = url
photoURL = linkedInfo.PhotoURL
draftID = &linkedInfo.DraftID
}
var itemsPreview string
@@ -679,6 +716,7 @@ func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]Unifie
IsAppCreated: isAppCreated,
PhotoURL: photoURL,
ItemsPreview: itemsPreview,
DraftID: draftID,
})
}
@@ -739,6 +777,14 @@ func (s *Service) CreateDraft(userID uuid.UUID) (*drafts.DraftInvoice, error) {
return nil, err
}
// Обновляем время последней активности сервера
if err := s.accountRepo.UpdateLastActivity(server.ID); err != nil {
logger.Log.Warn("Не удалось обновить время активности",
zap.String("server_id", server.ID.String()),
zap.Error(err))
// Не возвращаем ошибку - это некритично
}
return draft, nil
}
@@ -967,11 +1013,6 @@ func (s *Service) SaveDraftFull(draftID, userID uuid.UUID, req drafts.UpdateDraf
return err
}
// Проверяем, что черновик не завершен
if draft.Status == drafts.StatusCompleted {
return errors.New("черновик уже отправлен")
}
// Обновляем шапку черновика, если переданы поля
headerUpdated := false
@@ -1084,54 +1125,17 @@ func (s *Service) SaveDraftFull(draftID, userID uuid.UUID, req drafts.UpdateDraf
}
}
// Определяем, какое поле редактируется
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
currentItem.Quantity = decimal.NewFromFloat(*itemReq.Quantity)
}
price := decimal.Zero
if itemReq.Price != nil {
price = decimal.NewFromFloat(*itemReq.Price)
} else {
price = currentItem.Price
currentItem.Price = decimal.NewFromFloat(*itemReq.Price)
}
sum := decimal.Zero
if itemReq.Sum != nil {
sum = decimal.NewFromFloat(*itemReq.Sum)
} else {
sum = currentItem.Sum
currentItem.Sum = decimal.NewFromFloat(*itemReq.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
@@ -1142,14 +1146,12 @@ func (s *Service) SaveDraftFull(draftID, userID uuid.UUID, req drafts.UpdateDraf
// Сохраняем обновленную позицию
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,
"product_id": currentItem.ProductID,
"container_id": currentItem.ContainerID,
"quantity": currentItem.Quantity,
"price": currentItem.Price,
"sum": currentItem.Sum,
"is_matched": currentItem.IsMatched,
}
if err := s.draftRepo.UpdateItem(itemID, updates); err != nil {
@@ -1158,5 +1160,13 @@ func (s *Service) SaveDraftFull(draftID, userID uuid.UUID, req drafts.UpdateDraf
}
}
// Обновляем время последней активности сервера
if err := s.accountRepo.UpdateLastActivity(draft.RMSServerID); err != nil {
logger.Log.Warn("Не удалось обновить время активности",
zap.String("server_id", draft.RMSServerID.String()),
zap.Error(err))
// Не возвращаем ошибку - это некритично
}
return nil
}