diff --git a/ozon/common.go b/ozon/common.go index 064e319..5efe0df 100644 --- a/ozon/common.go +++ b/ozon/common.go @@ -809,3 +809,109 @@ const ( PaymentTypeGroupPaymentToCurrentAccount PaymentTypeGroupName = "payment to current account" PaymentTypeGroupSberpay PaymentTypeGroupName = "Sberpay" ) + +type VisualStatus string + +const ( + // dispute with the customer has been opened + VisualStatusDisputeOpened VisualStatus = "DisputeOpened" + + // pending with the seller + VisualStatusOnSellerApproval VisualStatus = "OnSellerApproval" + + // at the pick-up point + VisualStatusArrivedAtReturnPlace VisualStatus = "ArrivedAtReturnPlace" + + // pending clarification by the seller + VisualStatusOnSellerClarification VisualStatus = "OnSellerClarification" + + // pending clarification by the seller after partial compensation + VisualStatusOnSellerClarificationPartial VisualStatus = "OnSellerClarificationAfterPartialCompensation" + + // partial compensation offered + VisualStatusOfferedPartial VisualStatus = "OfferedPartialCompensation" + + // refund approved + VisualStatusReturnMoneyApproved VisualStatus = "ReturnMoneyApproved" + + // partial compensation provided + VisualStatusPartialReturned VisualStatus = "PartialCompensationReturned" + + // refund rejected, dispute isn't opened + VisualStatusCancelledDisputeNotOpen VisualStatus = "CancelledDisputeNotOpen" + + // request rejected + VisualStatusRejected VisualStatus = "Rejected" + + // request rejected by Ozon + VisualStatusCrmRejected VisualStatus = "CrmRejected" + + // request canceled + VisualStatusCancelled VisualStatus = "Cancelled" + + // request approved by the seller + VisualStatusApproved VisualStatus = "Approved" + + // request approved by Ozon + VisualStatusApprovedByOzon VisualStatus = "ApprovedByOzon" + + // seller received the return + VisualStatusReceivedBySeller VisualStatus = "ReceivedBySeller" + + // return is on its way to the seller + VisualStatusMovingToSeller VisualStatus = "MovingToSeller" + + // seller received the refund + VisualStatusReturnCompensated VisualStatus = "ReturnCompensated" + + // courier is taking the return to the seller + VisualStatusReturningByCourier VisualStatus = "ReturningByCourier" + + // on disposal + VisualStatusUtilizing VisualStatus = "Utilizing" + + // disposed of + VisualStatusUtilized VisualStatus = "Utilized" + + // customer received full refund + VisualStatusMoneyReturned VisualStatus = "MoneyReturned" + + // partial refund has been approved + VisualStatusPartialInProcess VisualStatus = "PartialCompensationInProcess" + + // seller opened a dispute + VisualStatusDisputeYouOpened VisualStatus = "DisputeYouOpened" + + // compensation rejected + VisualStatusCompensationRejected VisualStatus = "CompensationRejected" + + // support request sent + VisualStatusDisputeOpening VisualStatus = "DisputeOpening" + + // awaiting your decision on compensation + VisualStatusCompensationOffered VisualStatus = "CompensationOffered" + + // awaiting compensation + VisualStatusWaitingCompensation VisualStatus = "WaitingCompensation" + + // an error occurred when sending the support request + VisualStatusSendingError VisualStatus = "SendingError" + + // decision period has expired + VisualStatusCompensationRejectedBySla VisualStatus = "CompensationRejectedBySla" + + // seller has refused compensation + VisualStatusCompensationRejectedBySeller VisualStatus = "CompensationRejectedBySeller" + + // on the way to the Ozon warehouse + VisualStatusMovingToOzon VisualStatus = "MovingToOzon" + + // arrived at the Ozon warehouse + VisualStatusReturnedToOzon VisualStatus = "ReturnedToOzon" + + // quick refund + VisualStatusMoneyReturnedBySystem VisualStatus = "MoneyReturnedBySystem" + + // awaiting shipping + VisualStatusWaitingShipment VisualStatus = "WaitingShipment" +) diff --git a/ozon/rating.go b/ozon/rating.go index 8051e5b..97e50ac 100644 --- a/ozon/rating.go +++ b/ozon/rating.go @@ -18,6 +18,11 @@ type GetCurrentSellerRatingInfoResponse struct { // Rating groups list Groups []GetCurrentSellerRatingInfoGroup `json:"groups"` + // Localization index details. + // If you had no sales in the last 14 days, + // the parameter fields will be empty + LocalizationIndex []LocalizationIndex `json:"localization_index"` + // An indication that the penalty points balance is exceeded PenaltyScoreExceeded bool `json:"penalty_score_exceeded"` @@ -25,6 +30,14 @@ type GetCurrentSellerRatingInfoResponse struct { Premium bool `json:"premium"` } +type LocalizationIndex struct { + // Date of localization index calculation + CalculationDate time.Time `json:"calculation_date"` + + // Localization index value + LocalizationPercentage int32 `json:"localization_percentage"` +} + type GetCurrentSellerRatingInfoGroup struct { // Ratings group name GroupName string `json:"group_name"` diff --git a/ozon/rating_test.go b/ozon/rating_test.go index 3887a73..eb8c8d8 100644 --- a/ozon/rating_test.go +++ b/ozon/rating_test.go @@ -41,6 +41,12 @@ func TestGetCurrentRatingInfo(t *testing.T) { ] } ], + "localization_index": [ + { + "calculation_date": "2019-08-24T14:15:22Z", + "localization_percentage": 0 + } + ], "penalty_score_exceeded": true, "premium": true }`, diff --git a/ozon/returns.go b/ozon/returns.go index ca57a6c..3738bf3 100644 --- a/ozon/returns.go +++ b/ozon/returns.go @@ -943,7 +943,250 @@ func (c Returns) FBSQuantity(ctx context.Context, params *GetFBSQuantityReturnsP resp := &GetFBSQuantityReturnsResponse{} - response, err := c.client.Request(ctx, http.MethodPost, url, nil, resp, nil) + response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type ListReturnsParams struct { + // Filter + Filter *ListReturnsFilter `json:"filter,omitempty"` + + // Number of loaded returns. The maximum value is 500 + Limit int32 `json:"limit"` + + // Identifier of the last loaded return + LastId int64 `json:"last_id"` +} + +type ListReturnsFilter struct { + // Filter by return creation date + LogisticReturnDate *GetFBSReturnsFilterTimeRange `json:"logistic_return_date"` + + // Filter by storage fees start date + StorageTarifficationDate *GetFBSReturnsFilterTimeRange `json:"storage_tariffication_start_date"` + + // Filter by date the return status changed + VisualStatusChangeMoment *GetFBSReturnsFilterTimeRange `json:"visual_status_change_moment"` + + // Filter by order identifier + OrderId int64 `json:"order_id,omitempty"` + + // Filter by shipment number + PostingNumbers []string `json:"posting_numbers,omitempty"` + + // Filter by product name + ProductName string `json:"product_name,omitempty"` + + // Filter by product identifier in the seller's system + OfferId string `json:"offer_id,omitempty"` + + // Filter by return status + VisualStatusName VisualStatus `json:"visual_status_name,omitempty"` + + // Filter by warehouse identifier + WarehouseId int64 `json:"warehouse_id,omitempty"` + + // Filter by return label barcode + Barcode string `json:"barcode,omitempty"` + + // Filter by delivery scheme: FBS or FBO + ReturnSchema string `json:"return_schema,omitempty"` +} + +type ListReturnsResponse struct { + core.CommonResponse + + // Returns details + Returns []Return `json:"returns"` + + // true, if the seller has other returns + HasNext bool `json:"has_next"` +} + +type Return struct { + // Product items data + Exemplars []ReturnExemplar `json:"exemplars"` + + // Return identifier + Id int64 `json:"id"` + + // Company identifier + CompanyId int64 `json:"company_id"` + + // Return reason + ReturnReasonName string `json:"return_reason_name"` + + // Return type + Type string `json:"type"` + + // Return scheme + Schema string `json:"schema"` + + // Order identifier + OrderId int64 `json:"order_id"` + + // Order number + OrderNumber string `json:"order_number"` + + // Warehouse where the return is stored + Place ReturnPlace `json:"place"` + + // Warehouse where returns are sent to + TargetPlace ReturnPlace `json:"target_place"` + + // Storage details + Storage ReturnStorage `json:"storage"` + + // Product details + Product ReturnProduct `json:"product"` + + // Return details + Logistic ReturnLogistic `json:"logistic"` + + // Return status details + Visual ReturnVisual `json:"visual"` + + // Additional information + AdditionalInfo ReturnAdditionalInfo `json:"additional_info"` + + // Previous return identifier + SourceId int64 `json:"source_id"` + + // Shipment number + PostingNumber string `json:"posting_number"` + + // Original shipment barcode + ClearingId int64 `json:"clearing_id"` + + // Package unit identifier in the Ozon logistics system + ReturnClearingId int64 `json:"return_clearing_id"` +} + +type ReturnExemplar struct { + // Product identifier + Id int64 `json:"id"` +} + +type ReturnPlace struct { + // Warehouse identifier + Id int64 `json:"id"` + + // Warehouse name + Name string `json:"name"` + + // Warehouse address + Address string `json:"address"` +} + +type ReturnStorage struct { + // Storage cost details + Sum ReturnSum `json:"sum"` + + // First day of charging for storage + TarifficationsFirstDate time.Time `json:"tariffication_first_date"` + + // Start date for storage fees + TarifficationsStartDate time.Time `json:"tariffication_start_date"` + + // Date when the return was ready for handover + ArrivedMoment time.Time `json:"arrived_moment"` + + // Number of days the return has been waiting for handover + Days int64 `json:"days"` + + // Disposal cost details + UtilizationSum ReturnSum `json:"utilization_sum"` + + // Planned disposal date + UtilizationForecastDate string `json:"utilization_forecast_date"` +} + +type ReturnSum struct { + // Currency + CurrencyCode string `json:"currency_code"` + + // Disposal cost + Price float64 `json:"price"` +} + +type ReturnProduct struct { + // Product identifier in the Ozon system, SKU + SKU int64 `json:"sku"` + + // Product identifier in the seller's system + OfferId string `json:"offer_id"` + + // product name + Name string `json:"name"` + + // Product price details + Price ReturnSum `json:"price"` + + // Product cost without commission + PriceWithoutCommission ReturnSum `json:"price_without_commission"` + + // Sales commission by category + CommissionPercent float64 `json:"commission_percent"` + + // Commission details + Commission ReturnSum `json:"commission"` +} + +type ReturnLogistic struct { + // Date when the order was placed for technical return + TechnicalReturnMoment time.Time `json:"technical_return_moment"` + + // Date when the return arrived to the warehouse or was handed over to the seller + FinalMoment time.Time `json:"final_moment"` + + // Date when the seller received compensation for the return + CancelledWithCompensationMoment time.Time `json:"cancelled_with_compensation_moment"` + + // Date when the customer returned the product + ReturnDate time.Time `json:"return_date"` + + // Return label barcode + Barcode string `json:"barcode"` +} + +type ReturnVisual struct { + // Return status + Status ReturnVisualStatus `json:"status"` + + // Date the return status changed + ChangeMoment time.Time `json:"change_moment"` +} + +type ReturnVisualStatus struct { + // Return status identifier + Id int32 `json:"id"` + + // Return status name + DisplayName string `json:"display_name"` + + // System name of the return status + SystemName string `json:"sys_name"` +} + +type ReturnAdditionalInfo struct { + // true, if the return package is opened + IsOpened bool `json:"is_opened"` + + // true, if the return belongs to Super Economy products + IsSuperEconom bool `json:"is_super_econom"` +} + +func (c Returns) List(ctx context.Context, params *ListReturnsParams) (*ListReturnsResponse, error) { + url := "/v1/returns/list" + + resp := &ListReturnsResponse{} + + response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil) if err != nil { return nil, err } diff --git a/ozon/returns_test.go b/ozon/returns_test.go index 4623cbe..0e3eb30 100644 --- a/ozon/returns_test.go +++ b/ozon/returns_test.go @@ -1059,3 +1059,153 @@ func TestFBSQuantity(t *testing.T) { } } } + +func TestListReturns(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *ListReturnsParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &ListReturnsParams{ + Filter: &ListReturnsFilter{ + LogisticReturnDate: &GetFBSReturnsFilterTimeRange{ + TimeFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"), + TimeTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"), + }, + StorageTarifficationDate: &GetFBSReturnsFilterTimeRange{ + TimeFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"), + TimeTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"), + }, + VisualStatusChangeMoment: &GetFBSReturnsFilterTimeRange{ + TimeFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"), + TimeTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"), + }, + WarehouseId: 911, + ReturnSchema: "FBO", + ProductName: "string", + }, + Limit: 500, + LastId: 0, + }, + `{ + "returns": [ + { + "exemplars": [ + { + "id": 1019562967545956 + } + ], + "id": 1000015552, + "company_id": 3058, + "return_reason_name": "Customer refused on receipt: not satisfied with the quality of the product", + "type": "FullReturn", + "schema": "Fbs", + "order_id": 24540784250, + "order_number": "58544282-0057", + "place": { + "id": 23869688194000, + "name": "СЦ_Львовский_Возвраты", + "address": "Россия, обл. Московская, г. Подольск, промышленная зона Львовский, ул. Московская, д. 69, стр. 5" + }, + "target_place": { + "id": 23869688194000, + "name": "СЦ_Львовский_Возвраты", + "address": "Россия, обл. Московская, г. Подольск, промышленная зона Львовский, ул. Московская, д. 69, стр. 5" + }, + "storage": { + "sum": { + "currency_code": "RUB", + "price": 1231 + }, + "tariffication_first_date": "2024-07-30T06:15:48.998146Z", + "tariffication_start_date": "2024-07-29T06:15:48.998146Z", + "arrived_moment": "2024-07-29T06:15:48.998146Z", + "days": 0, + "utilization_sum": { + "currency_code": "RUB", + "price": 1231 + }, + "utilization_forecast_date": "2024-07-29T06:15:48.998146Z" + }, + "product": { + "sku": 1100526203, + "offer_id": "81451", + "name": "Кукла Дотти Плачущий младенец Cry Babies Dressy Dotty", + "price": { + "currency_code": "RUB", + "price": 3318 + }, + "price_without_commission": { + "currency_code": "RUB", + "price": 3318 + }, + "commission_percent": 1.2, + "commission": { + "currency_code": "RUB", + "price": 2312 + } + }, + "logistic": { + "technical_return_moment": "2024-07-29T06:15:48.998146Z", + "final_moment": "2024-07-29T06:15:48.998146Z", + "cancelled_with_compensation_moment": "2024-07-29T06:15:48.998146Z", + "return_date": "2024-07-29T06:15:48.998146Z", + "barcode": "ii5275210303" + }, + "visual": { + "status": { + "id": 3, + "display_name": "At the pick-up point", + "sys_name": "ArrivedAtReturnPlace" + }, + "change_moment": "2024-07-29T06:15:48.998146Z" + }, + "additional_info": { + "is_opened": true, + "is_super_econom": false + }, + "source_id": 90426223, + "posting_number": "58544282-0057-1", + "clearing_id": 21190893156000, + "return_clearing_id": null + } + ], + "has_next": false + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &ListReturnsParams{}, + `{ + "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)) + + ctx, _ := context.WithTimeout(context.Background(), testTimeout) + resp, err := c.Returns().List(ctx, test.params) + if err != nil { + t.Error(err) + continue + } + + compareJsonResponse(t, test.response, &ListReturnsResponse{}) + + if resp.StatusCode != test.statusCode { + t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode) + } + } +}