diff --git a/cmd/main.go b/cmd/main.go index 799d545..d742510 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -102,7 +102,7 @@ func main() { ocrHandler := handlers.NewOCRHandler(ocrService) recommendHandler := handlers.NewRecommendationsHandler(recService) settingsHandler := handlers.NewSettingsHandler(accountRepo, catalogRepo) - invoicesHandler := handlers.NewInvoiceHandler(invoicesService) + invoicesHandler := handlers.NewInvoiceHandler(invoicesService, syncService) // 8. Telegram Bot (Передаем syncService) if cfg.Telegram.Token != "" { @@ -176,6 +176,7 @@ func main() { // Invoices api.GET("/invoices/:id", invoicesHandler.GetInvoice) + api.POST("/invoices/sync", invoicesHandler.SyncInvoices) // Manual Sync Trigger api.POST("/sync/all", func(c *gin.Context) { diff --git a/config.yaml b/config.yaml index fba33e4..31acbd2 100644 --- a/config.yaml +++ b/config.yaml @@ -30,5 +30,5 @@ telegram: web_app_url: "https://rmser.serty.top" yookassa: - shop_id: "1236145" - secret_key: "test_HxUkDTirAycj7xooYcu_-gURsHMETbE_onIJYXGkj5Y" \ No newline at end of file + shop_id: "1234397" + secret_key: "live_bRlT9tJRi1hvP7_-C6xjdmzNHpaz9rIs9G0gzv6OPA0" \ No newline at end of file diff --git a/internal/services/sync/service.go b/internal/services/sync/service.go index 38d13b5..8bf685e 100644 --- a/internal/services/sync/service.go +++ b/internal/services/sync/service.go @@ -106,6 +106,31 @@ func (s *Service) SyncAllData(userID uuid.UUID, force bool) error { return nil } +// SyncInvoicesOnly запускает синхронизацию только накладных для конкретного пользователя +func (s *Service) SyncInvoicesOnly(userID uuid.UUID) error { + logger.Log.Info("Запуск синхронизации накладных", zap.String("user_id", userID.String())) + + // Получаем клиент и инфо о сервере + client, err := s.rmsFactory.GetClientForUser(userID) + if err != nil { + return err + } + server, err := s.accountRepo.GetActiveServer(userID) + if err != nil || server == nil { + return fmt.Errorf("active server not found for user %s", userID) + } + serverID := server.ID + + // Синхронизация накладных + if err := s.syncInvoices(client, serverID, false); err != nil { + logger.Log.Error("Sync Invoices failed", zap.Error(err)) + return err + } + + logger.Log.Info("Синхронизация накладных завершена", zap.String("user_id", userID.String())) + return nil +} + func (s *Service) syncSuppliers(c rms.ClientI, serverID uuid.UUID) error { list, err := c.FetchSuppliers() if err != nil { diff --git a/internal/transport/http/handlers/invoices.go b/internal/transport/http/handlers/invoices.go index caf3cf6..3264fa6 100644 --- a/internal/transport/http/handlers/invoices.go +++ b/internal/transport/http/handlers/invoices.go @@ -9,16 +9,18 @@ import ( "rmser/internal/services/drafts" invService "rmser/internal/services/invoices" + "rmser/internal/services/sync" "rmser/pkg/logger" ) type InvoiceHandler struct { service *invService.Service draftsService *drafts.Service + syncService *sync.Service } -func NewInvoiceHandler(service *invService.Service) *InvoiceHandler { - return &InvoiceHandler{service: service} +func NewInvoiceHandler(service *invService.Service, syncService *sync.Service) *InvoiceHandler { + return &InvoiceHandler{service: service, syncService: syncService} } // SendInvoice godoc @@ -85,3 +87,26 @@ func (h *InvoiceHandler) GetInvoice(c *gin.Context) { c.JSON(http.StatusOK, dto) } + +// SyncInvoices godoc +// @Summary Запустить синхронизацию накладных +// @Description Запускает синхронизацию накладных для пользователя +// @Tags invoices +// @Produce json +// @Success 200 {object} map[string]string +// @Failure 500 {object} map[string]string +func (h *InvoiceHandler) SyncInvoices(c *gin.Context) { + userID := c.MustGet("userID").(uuid.UUID) + + err := h.syncService.SyncInvoicesOnly(userID) + if err != nil { + logger.Log.Error("Ошибка синхронизации накладных", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Ошибка синхронизации"}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "status": "ok", + "message": "Синхронизация запущена", + }) +} diff --git a/rmser-view/src/pages/DraftsList.tsx b/rmser-view/src/pages/DraftsList.tsx index 7d60a72..5315887 100644 --- a/rmser-view/src/pages/DraftsList.tsx +++ b/rmser-view/src/pages/DraftsList.tsx @@ -2,11 +2,19 @@ import React, { useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { List, Typography, Tag, Spin, Empty, DatePicker, Flex } from "antd"; +import { + List, + Typography, + Tag, + Spin, + Empty, + DatePicker, + Flex, + Button, +} from "antd"; import { useNavigate } from "react-router-dom"; import { ArrowRightOutlined, - HistoryOutlined, CheckCircleOutlined, DeleteOutlined, PlusOutlined, @@ -14,53 +22,56 @@ import { LoadingOutlined, CloseCircleOutlined, StopOutlined, + SyncOutlined, + CloudServerOutlined, } from "@ant-design/icons"; import dayjs, { Dayjs } from "dayjs"; import { api } from "../services/api"; import type { UnifiedInvoice } from "../services/types"; const { Title, Text } = Typography; -const { RangePicker } = DatePicker; export const DraftsList: React.FC = () => { const navigate = useNavigate(); // Состояние фильтра дат: по умолчанию последние 7 дней - const [dateRange, setDateRange] = useState<[Dayjs, Dayjs]>([ - dayjs().subtract(7, "day"), - dayjs(), - ]); + const [startDate, setStartDate] = useState(dayjs().subtract(7, "day")); + const [endDate, setEndDate] = useState(dayjs()); + const [syncLoading, setSyncLoading] = useState(false); // Запрос данных с учетом дат (даты в ключе обеспечивают авто-перезапрос) const { data: invoices, isLoading, isError, + refetch, } = useQuery({ queryKey: [ "drafts", - dateRange[0].format("YYYY-MM-DD"), - dateRange[1].format("YYYY-MM-DD"), + startDate.format("YYYY-MM-DD"), + endDate.format("YYYY-MM-DD"), ], queryFn: () => api.getDrafts( - dateRange[0].format("YYYY-MM-DD"), - dateRange[1].format("YYYY-MM-DD") + startDate.format("YYYY-MM-DD"), + endDate.format("YYYY-MM-DD") ), staleTime: 0, refetchOnMount: true, refetchOnWindowFocus: true, }); - const getStatusTag = (item: UnifiedInvoice) => { - if (item.type === "SYNCED") { - return ( - } color="success"> - Синхронизировано - - ); + const handleSync = async () => { + setSyncLoading(true); + try { + await api.syncInvoices(); + refetch(); + } finally { + setSyncLoading(false); } + }; + const getStatusTag = (item: UnifiedInvoice) => { switch (item.status) { case "PROCESSING": return ( @@ -95,19 +106,19 @@ export const DraftsList: React.FC = () => { case "NEW": return ( } color="blue"> - Новый + Новая ); case "PROCESSED": return ( } color="green"> - Обработан + Проведена ); case "DELETED": return ( } color="red"> - Удален + Удалена ); default: @@ -133,9 +144,16 @@ export const DraftsList: React.FC = () => { return (
- - Накладные - + + + Накладные + +
diff --git a/rmser-view/src/pages/InvoiceViewPage.tsx b/rmser-view/src/pages/InvoiceViewPage.tsx index ea4fcd1..4bcbe98 100644 --- a/rmser-view/src/pages/InvoiceViewPage.tsx +++ b/rmser-view/src/pages/InvoiceViewPage.tsx @@ -78,17 +78,6 @@ export const InvoiceViewPage: React.FC = () => { key: "quantity", align: "right" as const, }, - { - title: "Цена", - dataIndex: "price", - key: "price", - align: "right" as const, - render: (price: number) => - price.toLocaleString("ru-RU", { - style: "currency", - currency: "RUB", - }), - }, { title: "Сумма", dataIndex: "total", @@ -187,10 +176,10 @@ export const InvoiceViewPage: React.FC = () => { size="small" summary={() => ( - + Итого: - + {totalSum.toLocaleString("ru-RU", { style: "currency", diff --git a/rmser-view/src/services/api.ts b/rmser-view/src/services/api.ts index becc171..7f940e3 100644 --- a/rmser-view/src/services/api.ts +++ b/rmser-view/src/services/api.ts @@ -251,4 +251,8 @@ export const api = { const { data } = await apiClient.get(`/invoices/${id}`); return data; }, + + syncInvoices: async (): Promise => { + await apiClient.post('/invoices/sync'); + }, }; \ No newline at end of file