diff --git a/ENDPOINTS.md b/ENDPOINTS.md index b292a05..0ac52e7 100644 --- a/ENDPOINTS.md +++ b/ENDPOINTS.md @@ -170,9 +170,9 @@ - [x] Stocks and products report (version 2) ## Finance -- [ ] Report on sold products +- [x] Report on sold products - [ ] Transactions list (version 3) -- [ ] Total transactions sum +- [x] Total transactions sum ## Seller rating - [x] Get information on current seller ratings diff --git a/core.go b/core.go index e9487c3..a3bb8c9 100644 --- a/core.go +++ b/core.go @@ -29,6 +29,7 @@ func (r Response) CopyCommonResponse(rhs *CommonResponse) { rhs.Code = r.Code rhs.Details = r.Details rhs.StatusCode = r.StatusCode + rhs.Message = r.Message } func getDefaultValues(v interface{}) (map[string]string, error) { diff --git a/ozon/finance.go b/ozon/finance.go new file mode 100644 index 0000000..04827d5 --- /dev/null +++ b/ozon/finance.go @@ -0,0 +1,244 @@ +package ozon + +import ( + "net/http" + "time" + + core "github.com/diphantxm/ozon-api-client" +) + +type ReportOnSoldProductsParams struct { + // Time period in the `YYYY-MM` format + Date string `json:"date"` +} + +type ReportOnSoldProductsResponse struct { + core.CommonResponse + + // Query result + Result []struct { + // Report title page + Header []struct { + // Report ID + Id string `json:"num"` + + // Report generation date + DocDate string `json:"doc_date"` + + // Date of the offer agreement + ContractDate string `json:"contract_date"` + + // Offer agreement number + ContractNum string `json:"contract_num"` + + // Currency of your prices + CurrencyCode string `json:"currency_code"` + + // Amount to accrue + DocAmount float64 `json:"doc_amount"` + + // Amount to accrue with VAT + VATAmount float64 `json:"vat_amount"` + + // Payer's TIN + PayerINN string `json:"payer_inn"` + + // Payer's Tax Registration Reason Code (KPP) + PayerKPP string `json:"payer_kpp"` + + // Payer's name + PayerName string `json:"payer_name"` + + // Recipient's TIN + RecipientINN string `json:"rcv_inn"` + + // Recipient's Tax Registration Reason Code (KPP) + RecipientKPP string `json:"rcv_kpp"` + + // Recipient's name + RecipientName string `json:"rcv_name"` + + // Period start in the report + StartDate string `json:"start_date"` + + // Period end in the report + StopDate string `json:"stop_date"` + } `json:"header"` + + // Report table + Rows []struct { + // Row number + RowNumber int32 `json:"row_number"` + + // Product ID + ProductId int64 `json:"product_id"` + + // Product name + ProductName string `json:"product_name"` + + // Product barcode + Barcode string `json:"barcode"` + + // Product identifier in the seller's system + OfferId string `json:"offer_id"` + + // Sales commission by category + CommissionPercent float64 `json:"commission_percent"` + + // Seller's price with their discount + Price float64 `json:"price"` + + // Selling price: the price at which the customer purchased the product. For sold products + PriceSale float64 `json:"price_sale"` + + // Sold for amount. + // + // Sold products cost considering the quantity and regional coefficients. Calculation is made by the sale_amount price + SaleAmount float64 `json:"sale_amount"` + + // Commission for sold products, including discounts and extra charges + SaleCommission float64 `json:"sale_commission"` + + // Extra charge at the expense of Ozon. + // + // Amount that Ozon will compensate the seller if the Ozon discount is greater than or equal to the sales commission + SaleDiscount float64 `json:"sale_discount"` + + // Total accrual for the products sold. + // + // Amount after deduction of sales commission, application of discounts and extra charges + SalePriceSeller float64 `json:"sale_price_seller"` + + // Quantity of products sold at the price_sale price + SaleQuantity int32 `json:"sale_qty"` + + // Price at which the customer purchased the product. For returned products + ReturnSale float64 `json:"return_sale"` + + // Cost of returned products, taking into account the quantity and regional coefficients. + // Calculation is carried out at the return_sale price + ReturnAmount float64 `json:"return_amount"` + + // Commission including the quantity of products, discounts and extra charges. + // Ozon compensates it for the returned products + ReturnCommission float64 `json:"return_commission"` + + // Extra charge at the expense of Ozon. + // + // Amount of the discount at the expense of Ozon on returned products. + // Ozon will compensate it to the seller if the Ozon discount is greater than or equal to the sales commission + ReturnDiscount float64 `json:"return_discount"` + + // Amount charged to the seller for returned products after deducing sales commissions, applying discounts and extra charges + ReturnPriceSeller float64 `json:"return_price_seller"` + + // Quantity of returned products + ReturnQuantity int32 `json:"return_qty"` + } `json:"rows"` + } `json:"result"` +} + +// Returns information on products sold and returned within a month. Canceled or non-purchased products are not included. +// +// Report is returned no later than the 5th day of the next month +func (c Client) ReportOnSoldProducts(params *ReportOnSoldProductsParams) (*ReportOnSoldProductsResponse, error) { + url := "/v1/finance/realization" + + resp := &ReportOnSoldProductsResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type GetTotalTransactionsSumParams struct { + // Filter by date + Date GetTotalTransactionsSumDate `json:"date"` + + // Shipment number + PostingNumber string `json:"posting_number"` + + // Transaction type: + // + // - all — all, + // - orders — orders, + // - returns — returns and cancellations, + // - services — service fees, + // - compensation — compensation, + // - transferDelivery — delivery cost, + // - other — other + TransactionType string `json:"transaction_type"` +} + +type GetTotalTransactionsSumDate struct { + // Period start. + // + // Format: YYYY-MM-DDTHH:mm:ss.sssZ. + // Example: 2019-11-25T10:43:06.51 + From time.Time `json:"from"` + + // Period end. + // + // Format: YYYY-MM-DDTHH:mm:ss.sssZ. + // Example: 2019-11-25T10:43:06.51 + To time.Time `json:"to"` +} + +type GetTotalTransactionsSumResponse struct { + core.CommonResponse + + // Method result + Result struct { + // Total cost of products and returns for specified period + AccrualsForSale float64 `json:"accruals_for_sale"` + + // Compensations + CompensationAmount float64 `json:"compensatino_amount"` + + // Charges for delivery and returns when working under rFBS scheme + MoneyTransfer float64 `json:"money_transfer"` + + // Other accurals + OthersAmount float64 `json:"others_amount"` + + // Cost of shipment processing, orders packaging, pipeline and last mile services, and delivery cost before the new commissions and rates applied from February 1, 2021. + // + // Pipeline is delivery of products from one cluster to another. + // + // Last mile is products delivery to the pick-up point, parcle terminal, or by courier + ProcessingAndDelivery float64 `json:"processing_and_delivery"` + + // Cost of reverse pipeline, returned, canceled and unredeemed orders processing, and return cost before the new commissions and rates applied from February 1, 2021. + // + // Pipeline is delivery of products from one cluster to another. + // + // Last mile is products delivery to the pick-up point, parcle terminal, or by courier + RefundsAndCancellations float64 `json:"refunds_and_cancellations"` + + // The commission withheld when the product was sold and refunded when the product was returned + SaleCommission float64 `json:"sale_commission"` + + // The additional services cost that are not directly related to deliveries and returns. + // For example, promotion or product placement + ServicesAmount float64 `json:"services_amount"` + } `json:"result"` +} + +// Returns total sums for transactions for specified period +func (c Client) GetTotalTransactionsSum(params *GetTotalTransactionsSumParams) (*GetTotalTransactionsSumResponse, error) { + url := "/v3/finance/transaction/totals" + + resp := &GetTotalTransactionsSumResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} diff --git a/ozon/finance_test.go b/ozon/finance_test.go new file mode 100644 index 0000000..674a983 --- /dev/null +++ b/ozon/finance_test.go @@ -0,0 +1,172 @@ +package ozon + +import ( + "net/http" + "testing" + + core "github.com/diphantxm/ozon-api-client" +) + +func TestReportOnSoldProducts(t *testing.T) { + tests := []struct { + statusCode int + headers map[string]string + params *ReportOnSoldProductsParams + response string + errorMessage string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &ReportOnSoldProductsParams{ + Date: "2022-09", + }, + `{ + "result": [ + { + "header": [ + { + "doc_date": "2022-09-22", + "num": "string", + "start_date": "2022-09-02", + "stop_date": "2022-09-22", + "contract_date": "2022-09-02", + "contract_num": "string", + "payer_name": "string", + "payer_inn": "string", + "payer_kpp": "string", + "rcv_name": "string", + "rcv_inn": "string", + "rcv_kpp": "string", + "doc_amount": 1, + "vat_amount": 1, + "currency_code": "string" + } + ], + "rows": [ + { + "row_number": 0, + "product_id": 0, + "product_name": "string", + "offer_id": "string", + "barcode": "string", + "price": 0, + "commission_percent": 0, + "price_sale": 0, + "sale_qty": 0, + "sale_amount": 0, + "sale_discount": 0, + "sale_commission": 0, + "sale_price_seller": 0, + "return_sale": 0, + "return_qty": 0, + "return_amount": 0, + "return_discount": 0, + "return_commission": 0, + "return_price_seller": 0 + } + ] + } + ] + }`, + "", + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &ReportOnSoldProductsParams{}, + `{ + "code": 16, + "message": "Client-Id and Api-Key headers are required" + }`, + "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.ReportOnSoldProducts(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 resp.Message != test.errorMessage { + t.Errorf("got wrong error message: got: %s, expected: %s", resp.Message, test.errorMessage) + } + } + } +} + +func TestGetTotalTransactionsSum(t *testing.T) { + tests := []struct { + statusCode int + headers map[string]string + params *GetTotalTransactionsSumParams + response string + errorMessage string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &GetTotalTransactionsSumParams{ + Date: GetTotalTransactionsSumDate{ + From: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-01T00:00:00.000Z"), + To: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-02T00:00:00.000Z"), + }, + TransactionType: "ALL", + }, + `{ + "result": { + "accruals_for_sale": 96647.58, + "sale_commission": -11456.65, + "processing_and_delivery": -24405.68, + "refunds_and_cancellations": -330, + "services_amount": -1307.57, + "compensation_amount": 0, + "money_transfer": 0, + "others_amount": 113.05 + } + }`, + "", + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &GetTotalTransactionsSumParams{}, + `{ + "code": 16, + "message": "Client-Id and Api-Key headers are required" + }`, + "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.GetTotalTransactionsSum(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 resp.Message != test.errorMessage { + t.Errorf("got wrong error message: got: %s, expected: %s", resp.Message, test.errorMessage) + } + } + } +}