diff --git a/ozon/common.go b/ozon/common.go index 8a8e186..63d8090 100644 --- a/ozon/common.go +++ b/ozon/common.go @@ -175,3 +175,141 @@ const ( // overdue Overdue SupplyRequestState = "OVERDUE" ) + +type ShipmentStatus string + +const ( + // acceptance is in progress + AcceptanceInProgress ShipmentStatus = "acceptance_in_progress" + + // arbitration + Arbitration ShipmentStatus = "arbitration" + + // awaiting confirmation + AwaitingApprove ShipmentStatus = "awaiting_approve" + + // awaiting shipping + AwaitingDeliver ShipmentStatus = "awaiting_deliver" + + // awaiting packaging + AwaitingPackaging ShipmentStatus = "awaiting_packaging" + + // created + AwaitingVerification ShipmentStatus = "awaiting_verification" + + // cancelled + CancelledSubstatus ShipmentStatus = "cancelled" + + // delivered + Delivered ShipmentStatus = "delivered" + + // delivery is in progress + Delivering ShipmentStatus = "delivering" + + // picked up by driver + DriverPickup ShipmentStatus = "driver_pickup" + + // not accepted at the sorting center + NotAccepted ShipmentStatus = "not_accepted" + + // sent by the seller + SentBySeller ShipmentStatus = "sent_by_seller" +) + +type ShipmentSubstatus string + +const ( + // acceptance in progress + PostingAcceptanceInProgress ShipmentStatus = "posting_acceptance_in_progress" + + // arbitrage + PostingInArbitration ShipmentStatus = "posting_in_arbitration" + + // created + PostingCreated ShipmentStatus = "posting_created" + + // in the freight + PostingInCarriage ShipmentStatus = "posting_in_carriage" + + // not added to the freight + PostingNotInCarriage ShipmentStatus = "posting_not_in_carriage" + + // registered + PostingRegistered ShipmentStatus = "posting_registered" + + // is handed over to the delivery service + PostingTransferringToDelivery ShipmentStatus = "posting_transferring_to_delivery" + + // waiting for passport data + PostingAwaitingPassportData ShipmentStatus = "posting_awaiting_passport_data" + + // created + PostingCreatedSubstatus ShipmentStatus = "posting_created" + + // awaiting registration + PostingAwaitingRegistration ShipmentStatus = "posting_awaiting_registration" + + // registration error + PostingRegistrationError ShipmentStatus = "posting_registration_error" + + // created + PostingSplitPending ShipmentStatus = "posting_split_pending" + + // canceled + PostingCancelled ShipmentStatus = "posting_canceled" + + // customer delivery arbitrage + PostingInClientArbitration ShipmentStatus = "posting_in_client_arbitration" + + // delivered + PostingDelivered ShipmentStatus = "posting_delivered" + + // recieved + PostingReceived ShipmentStatus = "posting_received" + + // presumably delivered + PostingConditionallyDelivered ShipmentStatus = "posting_conditionally_delivered" + + // courier on the way + PostingInCourierService ShipmentStatus = "posting_in_courier_service" + + // at the pick-up point + PostingInPickupPoint ShipmentStatus = "posting_in_pickup_point" + + // on the way to the city + PostingOnWayToCity ShipmentStatus = "posting_on_way_to_city" + + // on the way to the pick-up point + PostingOnWayToPickupPoint ShipmentStatus = "posting_on_way_to_pickup_point" + + // returned to the warehouse + PostingReturnedToWarehouse ShipmentStatus = "posting_returned_to_warehouse" + + // is handed over to the courier + PostingTransferredToCourierService ShipmentStatus = "posting_transferred_to_courier_service" + + // handed over to the driver + PostingDriverPickup ShipmentStatus = "posting_driver_pick_up" + + // not accepted at the sorting center + PostingNotInSortCenter ShipmentStatus = "posting_not_in_sort_center" + + // sent by the seller + SentBySellerSubstatus ShipmentStatus = "sent_by_seller" +) + +type TPLIntegrationType string + +const ( + // delivery by the Ozon logistics + OzonTPLType TPLIntegrationType = "ozon" + + // delivery by a third-party service, Ozon registers the order + AggregatorTPLType TPLIntegrationType = "aggregator" + + // delivery by a third-party service, the seller registers the order + TrackingTPLType TPLIntegrationType = "3pl_tracking" + + // delivery by the seller + NonIntegratedTPLType TPLIntegrationType = "non_integrated" +) diff --git a/ozon/fbs.go b/ozon/fbs.go index 9c73c15..e3f2c96 100644 --- a/ozon/fbs.go +++ b/ozon/fbs.go @@ -754,14 +754,13 @@ type GetShipmentDataByIdentifierResponse struct { ShipmentDate time.Time `json:"shipment_date"` // Shipment status - Status string `json:"status"` + Status ShipmentStatus `json:"status"` - // Type of integration with the delivery service: - // - ozon — delivery by the Ozon logistics. - // - aggregator — delivery by a third-party service, Ozon registers the order. - // - 3pl_tracking — delivery by a third-party service, the seller registers the order. - // - non_integrated — delivery by the seller - TPLIntegrationType string `json:"tpl_integration_type"` + // Shipment substatus + Substatus ShipmentSubstatus `json:"substatus"` + + // Type of integration with the delivery service + TPLIntegrationType TPLIntegrationType `json:"tpl_integration_type"` // Shipment tracking number TrackingNumber string `json:"tracking_number"` @@ -1132,6 +1131,7 @@ type CancelShipmentResponse struct { // - 665 — the customer did not pick the order; // - 666 — delivery is not available in the region; // - 667 — order was lost by the delivery service. +// // For presumably delivered orders only the last 3 reasons are available. // // If cancel_reason_id parameter value is 402, fill the cancel_reason_message field @@ -1381,13 +1381,14 @@ type CheckProductItemsDataResponse struct { // Asynchronous method: // - for checking the availability of product items in the “Chestny ZNAK” labeling system // - for saving product items data +// // To get the checks results, use the `/v4/fbs/posting/product/exemplar/status method` // // If necessary, specify the number of the cargo customs declaration in the gtd parameter. If it is missing, pass the value `is_gtd_absent` = true // // If you have multiple identical products in a shipment, specify one `product_id` and `exemplars` array for each product in the shipment // -// Always pass a complete set of product items data +// # Always pass a complete set of product items data // // For example, you have 10 product items in your system. // You have passed them for checking and saving. Then they added another 60 product items to your system. diff --git a/ozon/products.go b/ozon/products.go index 5aadc9e..4249b2a 100644 --- a/ozon/products.go +++ b/ozon/products.go @@ -223,7 +223,51 @@ type ProductDetails struct { // Product price including discounts. This value is shown on the product description page Price string `json:"price"` - // Price index. Learn more in Help Center + // Product price indexes + PriceIndexes struct { + // Competitors' product price on other marketplaces + ExternalIndexData struct { + // Minimum competitors' product price on other marketplaces + MinimalPrice string `json:"minimal_price"` + + // Price currency + MinimalPriceCurrency string `json:"minimal_price_currency"` + + // Price index value + PriceIndexValue float64 `json:"price_index_value"` + } `json:"external_index_data"` + + // Competitors' product price on Ozon + OzonIndexData struct { + // Minimum competitors' product price on Ozon + MinimalPrice string `json:"minimal_price"` + + // Price currency + MinimalPriceCurrency string `json:"minimal_price_currency"` + + // Price index value + PriceIndexValue float64 `json:"price_index_value"` + } `json:"ozon_index_data"` + + // Resulting price index of the product + PriceIndex string `json:"price_index"` + + // Price of your product on other marketplaces + SelfMarketplaceIndexData struct { + // Minimum price of your product on other marketplaces + MinimalPrice string `json:"minimal_price"` + + // Price currency + MinimalPriceCurrency string `json:"minimal_price_currency"` + + // Price index value + PriceIndexValue float64 `json:"price_index_value"` + } `json:"self_marketplace_index_data"` + } `json:"prices_indexes"` + + // Deprecated: Price index. Learn more in Help Center + // + // Use PriceIndexes instead PriceIndex string `json:"price_idnex"` // Product price suggested by the system based on similar offers @@ -1445,6 +1489,7 @@ type GetProductRangeLimitUploadQuota struct { // - Product range limit: how many products you can create in your personal account. // - Products creation limit: how many products you can create per day. // - Products update limit: how many products you can update per day. +// // If you have a product range limit and you exceed it, you won't be able to create new products func (c Products) GetProductRangeLimit() (*GetProductRangeLimitResponse, error) { url := "/v4/product/info/limit" @@ -1910,7 +1955,7 @@ type GetMarkdownInfoResponse struct { core.CommonResponse // Information about the markdown and the main product - Items []struct{ + Items []struct { // Comment on the damage reason CommentReasonDamaged string `json:"comment_reason_damaged"` @@ -1975,7 +2020,7 @@ func (c Products) GetMarkdownInfo(params *GetMarkdownInfoParams) (*GetMarkdownIn return resp, nil } -type SetDiscountOnMarkdownProductParams struct{ +type SetDiscountOnMarkdownProductParams struct { // Discount amount: from 3 to 99 percents Discount int32 `json:"discount"` @@ -1983,7 +2028,7 @@ type SetDiscountOnMarkdownProductParams struct{ ProductId int64 `json:"product_id"` } -type SetDiscountOnMarkdownProductResponse struct{ +type SetDiscountOnMarkdownProductResponse struct { core.CommonResponse // Method result. true if the query was executed without errors @@ -2004,3 +2049,36 @@ func (c Products) SetDiscountOnMarkdownProduct(params *SetDiscountOnMarkdownProd return resp, nil } + +type NumberOfSubsToProductAvailabilityParams struct { + // List of SKUs, product identifiers in the Ozon system + SKUS []int64 `json:"skus"` +} + +type NumberOfSubsToProductAvailabilityResponse struct { + core.CommonResponse + + // Method result + Result []struct { + // Number of subscribed users + Count int64 `json:"count"` + + // Product identifier in the Ozon system, SKU + SKU int64 `json:"sku"` + } `json:"result"` +} + +// You can pass multiple products in a request +func (c Products) NumberOfSubsToProductAvailability(params *NumberOfSubsToProductAvailabilityParams) (*NumberOfSubsToProductAvailabilityResponse, error) { + url := "/v1/product/info/subscription" + + resp := &NumberOfSubsToProductAvailabilityResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp, nil) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} diff --git a/ozon/products_test.go b/ozon/products_test.go index feae8fb..1d9275c 100644 --- a/ozon/products_test.go +++ b/ozon/products_test.go @@ -168,7 +168,24 @@ func TestGetProductDetails(t *testing.T) { "has_stock": false, "active_product": false }, - "price_index": "0.00", + "price_indexes": { + "external_index_data": { + "minimal_price": "string", + "minimal_price_currency": "string", + "price_index_value": 0 + }, + "ozon_index_data": { + "minimal_price": "string", + "minimal_price_currency": "string", + "price_index_value": 0 + }, + "price_index": "WITHOUT_INDEX", + "self_marketplaces_index_data": { + "minimal_price": "string", + "minimal_price_currency": "string", + "price_index_value": 0 + } + }, "commissions": [], "volume_weight": 0.1, "is_prepayment": false, @@ -233,7 +250,7 @@ func TestGetProductDetails(t *testing.T) { "state_updated_at": "2021-10-21T15:48:03.927309Z" } } - }`, + }`, }, // Test No Client-Id or Api-Key { @@ -2407,3 +2424,66 @@ func TestSetDiscountOnMarkdownProduct(t *testing.T) { } } } + +func TestNumberOfSubsToProductAvailability(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *NumberOfSubsToProductAvailabilityParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &NumberOfSubsToProductAvailabilityParams{ + SKUS: []int64{1234}, + }, + `{ + "result": [ + { + "count": 2, + "sku": 1234 + } + ] + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &NumberOfSubsToProductAvailabilityParams{}, + `{ + "code": 16, + "message": "Client-Id and Api-Key headers are required" + }`, + }, + } + + for _, test := range tests { + c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) + + resp, err := c.Products().NumberOfSubsToProductAvailability(test.params) + if err != nil { + t.Error(err) + } + + if resp.StatusCode != test.statusCode { + t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode) + } + + if resp.StatusCode == http.StatusOK { + if len(resp.Result) != len(test.params.SKUS) { + t.Errorf("Length of SKUS in request and response are not equal") + } + + if len(resp.Result) > 0 { + if resp.Result[0].SKU != test.params.SKUS[0] { + t.Errorf("SKU in request and response are not equal") + } + } + } + } +}