mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
Полноценно редактируются черновики
Добавляются фасовки как в черновике, так и в обучении Исправил внешний вид
This commit is contained in:
@@ -38,6 +38,8 @@ type ClientI interface {
|
||||
FetchInvoices(from, to time.Time) ([]invoices.Invoice, error)
|
||||
FetchStoreOperations(presetID string, from, to time.Time) ([]StoreReportItemXML, error)
|
||||
CreateIncomingInvoice(inv invoices.Invoice) (string, error)
|
||||
GetProductByID(id uuid.UUID) (*ProductFullDTO, error)
|
||||
UpdateProduct(product ProductFullDTO) (*ProductFullDTO, error)
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
@@ -571,14 +573,20 @@ func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) {
|
||||
price, _ := item.Price.Float64()
|
||||
sum, _ := item.Sum.Float64()
|
||||
|
||||
reqDTO.ItemsWrapper.Items = append(reqDTO.ItemsWrapper.Items, IncomingInvoiceImportItemXML{
|
||||
xmlItem := IncomingInvoiceImportItemXML{
|
||||
ProductID: item.ProductID.String(),
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
Sum: sum,
|
||||
Num: i + 1,
|
||||
Store: inv.DefaultStoreID.String(),
|
||||
})
|
||||
}
|
||||
|
||||
if item.ContainerID != nil && *item.ContainerID != uuid.Nil {
|
||||
xmlItem.ContainerId = item.ContainerID.String()
|
||||
}
|
||||
|
||||
reqDTO.ItemsWrapper.Items = append(reqDTO.ItemsWrapper.Items, xmlItem)
|
||||
}
|
||||
|
||||
// 2. Маршалинг в XML
|
||||
@@ -613,7 +621,6 @@ func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) {
|
||||
zap.String("url", fullURL),
|
||||
zap.String("body_payload", string(xmlPayload)),
|
||||
)
|
||||
// ----------------------------------------
|
||||
|
||||
// 5. Отправка
|
||||
req, err := http.NewRequest("POST", fullURL, bytes.NewReader(xmlPayload))
|
||||
@@ -659,3 +666,92 @@ func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) {
|
||||
|
||||
return result.DocumentNumber, nil
|
||||
}
|
||||
|
||||
// GetProductByID получает полную структуру товара по ID (через /list?ids=...)
|
||||
func (c *Client) GetProductByID(id uuid.UUID) (*ProductFullDTO, error) {
|
||||
// Параметр ids должен быть списком. iiko ожидает ids=UUID
|
||||
params := map[string]string{
|
||||
"ids": id.String(),
|
||||
"includeDeleted": "false",
|
||||
}
|
||||
|
||||
resp, err := c.doRequest("GET", "/resto/api/v2/entities/products/list", params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request error: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("rms error code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Ответ - это массив товаров
|
||||
var products []ProductFullDTO
|
||||
if err := json.NewDecoder(resp.Body).Decode(&products); err != nil {
|
||||
return nil, fmt.Errorf("json decode error: %w", err)
|
||||
}
|
||||
|
||||
if len(products) == 0 {
|
||||
return nil, fmt.Errorf("product not found in rms")
|
||||
}
|
||||
|
||||
return &products[0], nil
|
||||
}
|
||||
|
||||
// UpdateProduct отправляет полную структуру товара на обновление (/update)
|
||||
func (c *Client) UpdateProduct(product ProductFullDTO) (*ProductFullDTO, error) {
|
||||
// Маршалим тело
|
||||
bodyBytes, err := json.Marshal(product)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("json marshal error: %w", err)
|
||||
}
|
||||
|
||||
// Используем doRequestPost (надо реализовать или вручную, т.к. doRequest у нас GET-ориентирован в текущем коде был прост)
|
||||
// Расширим логику doRequest или напишем тут, т.к. это POST с JSON body
|
||||
if err := c.ensureToken(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.mu.RLock()
|
||||
token := c.token
|
||||
c.mu.RUnlock()
|
||||
|
||||
endpoint := c.baseURL + "/resto/api/v2/entities/products/update?key=" + token
|
||||
|
||||
req, err := http.NewRequest("POST", endpoint, bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("update failed (code %d): %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var result UpdateEntityResponse
|
||||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||
return nil, fmt.Errorf("response unmarshal error: %w", err)
|
||||
}
|
||||
|
||||
if result.Result != "SUCCESS" {
|
||||
// Собираем ошибки
|
||||
errMsg := "rms update error: "
|
||||
for _, e := range result.Errors {
|
||||
errMsg += fmt.Sprintf("[%s] %s; ", e.Code, e.Value)
|
||||
}
|
||||
return nil, fmt.Errorf(errMsg)
|
||||
}
|
||||
|
||||
if result.Response == nil {
|
||||
return nil, fmt.Errorf("empty response from rms after update")
|
||||
}
|
||||
|
||||
return result.Response, nil
|
||||
}
|
||||
|
||||
@@ -74,6 +74,66 @@ type AssemblyItemDTO struct {
|
||||
AmountOut float64 `json:"amountOut"`
|
||||
}
|
||||
|
||||
// ProductFullDTO используется для получения (list?ids=...) и обновления (update) товара целиком.
|
||||
type ProductFullDTO struct {
|
||||
ID string `json:"id"`
|
||||
Deleted bool `json:"deleted"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Num string `json:"num"`
|
||||
Code string `json:"code"`
|
||||
Parent *string `json:"parent"` // null или UUID
|
||||
Modifiers []interface{} `json:"modifiers"` // Оставляем interface{}, чтобы не мапить сложную структуру, если не меняем её
|
||||
TaxCategory *string `json:"taxCategory"`
|
||||
Category *string `json:"category"`
|
||||
AccountingCategory *string `json:"accountingCategory"`
|
||||
Color map[string]int `json:"color"`
|
||||
FontColor map[string]int `json:"fontColor"`
|
||||
FrontImageID *string `json:"frontImageId"`
|
||||
Position *int `json:"position"`
|
||||
ModifierSchemaID *string `json:"modifierSchemaId"`
|
||||
MainUnit string `json:"mainUnit"` // Обязательное поле
|
||||
ExcludedSections []string `json:"excludedSections"` // Set<UUID>
|
||||
DefaultSalePrice float64 `json:"defaultSalePrice"`
|
||||
PlaceType *string `json:"placeType"`
|
||||
DefaultIncInMenu bool `json:"defaultIncludedInMenu"`
|
||||
Type string `json:"type"` // GOODS, DISH...
|
||||
UnitWeight float64 `json:"unitWeight"`
|
||||
UnitCapacity float64 `json:"unitCapacity"`
|
||||
StoreBalanceLevels []StoreBalanceLevel `json:"storeBalanceLevels"`
|
||||
UseBalanceForSell bool `json:"useBalanceForSell"`
|
||||
Containers []ContainerFullDTO `json:"containers"`
|
||||
ProductScaleID *string `json:"productScaleId"`
|
||||
Barcodes []interface{} `json:"barcodes"`
|
||||
ColdLossPercent float64 `json:"coldLossPercent"`
|
||||
HotLossPercent float64 `json:"hotLossPercent"`
|
||||
OuterCode *string `json:"outerEconomicActivityNomenclatureCode"`
|
||||
AllergenGroups *string `json:"allergenGroups"`
|
||||
EstPurchasePrice float64 `json:"estimatedPurchasePrice"`
|
||||
CanSetOpenPrice bool `json:"canSetOpenPrice"`
|
||||
NotInStoreMovement bool `json:"notInStoreMovement"`
|
||||
}
|
||||
|
||||
type StoreBalanceLevel struct {
|
||||
StoreID string `json:"storeId"`
|
||||
MinBalanceLevel *float64 `json:"minBalanceLevel"`
|
||||
MaxBalanceLevel *float64 `json:"maxBalanceLevel"`
|
||||
}
|
||||
|
||||
type ContainerFullDTO struct {
|
||||
ID *string `json:"id,omitempty"` // При создании новой фасовки ID пустой/null
|
||||
Num string `json:"num"` // Порядковый номер? Обычно строка.
|
||||
Name string `json:"name"`
|
||||
Count float64 `json:"count"`
|
||||
MinContainerWeight float64 `json:"minContainerWeight"`
|
||||
MaxContainerWeight float64 `json:"maxContainerWeight"`
|
||||
ContainerWeight float64 `json:"containerWeight"`
|
||||
FullContainerWeight float64 `json:"fullContainerWeight"`
|
||||
BackwardRecalculation bool `json:"backwardRecalculation"`
|
||||
Deleted bool `json:"deleted"`
|
||||
UseInFront bool `json:"useInFront"`
|
||||
}
|
||||
|
||||
// --- XML DTOs (Legacy API) ---
|
||||
|
||||
type IncomingInvoiceListXML struct {
|
||||
@@ -149,15 +209,14 @@ type IncomingInvoiceImportXML struct {
|
||||
}
|
||||
|
||||
type IncomingInvoiceImportItemXML struct {
|
||||
ProductID string `xml:"product"` // GUID товара
|
||||
Amount float64 `xml:"amount"` // Кол-во в базовых единицах
|
||||
Price float64 `xml:"price"` // Цена за единицу
|
||||
Sum float64 `xml:"sum,omitempty"`
|
||||
Store string `xml:"store"` // GUID склада
|
||||
// Поля ниже можно опустить, если iiko должна сама подтянуть их из карточки товара
|
||||
// или если мы работаем в базовых единицах.
|
||||
AmountUnit string `xml:"amountUnit,omitempty"` // GUID единицы измерения
|
||||
Num int `xml:"num,omitempty"` // Номер строки
|
||||
ProductID string `xml:"product"` // GUID товара
|
||||
Amount float64 `xml:"amount"` // Кол-во (в фасовках, если указан containerId)
|
||||
Price float64 `xml:"price"` // Цена за единицу (за фасовку, если указан containerId)
|
||||
Sum float64 `xml:"sum,omitempty"` // Сумма
|
||||
Store string `xml:"store"` // GUID склада
|
||||
ContainerId string `xml:"containerId,omitempty"` // ID фасовки
|
||||
AmountUnit string `xml:"amountUnit,omitempty"` // GUID единицы измерения (можно опустить, если фасовка)
|
||||
Num int `xml:"num,omitempty"`
|
||||
}
|
||||
|
||||
// DocumentValidationResult описывает ответ сервера при импорте
|
||||
@@ -170,3 +229,17 @@ type DocumentValidationResult struct {
|
||||
ErrorMessage string `xml:"errorMessage"`
|
||||
AdditionalInfo string `xml:"additionalInfo"`
|
||||
}
|
||||
|
||||
// --- Вспомогательные DTO для ответов (REST) ---
|
||||
|
||||
// UpdateEntityResponse - ответ на /save или /update
|
||||
type UpdateEntityResponse struct {
|
||||
Result string `json:"result"` // "SUCCESS" or "ERROR"
|
||||
Response *ProductFullDTO `json:"response"`
|
||||
Errors []ErrorDTO `json:"errors"`
|
||||
}
|
||||
|
||||
type ErrorDTO struct {
|
||||
Code string `json:"code"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user