From 962ff0b1da1a33f1d6255dc779d815a51672b350 Mon Sep 17 00:00:00 2001 From: Neur0toxine Date: Wed, 2 Jun 2021 17:00:32 +0300 Subject: [PATCH] New API client --- .editorconfig | 15 + .env.dist | 6 + .github/workflows/ci.yml | 25 +- .github/workflows/code_quality.yml | 42 + .github/workflows/documentation.yml | 47 + .gitignore | 18 +- README.md | 332 +- bin/retailcrm-client | 49 + composer.json | 97 +- doc/compilation_prompt.md | 83 + doc/customization/customization.md | 19 + .../different_psr_implementations.md | 69 + .../pipelines/implementing_a_handler.md | 23 + .../pipelines/using_a_predefined_handler.md | 292 + doc/index.md | 29 + doc/structure.md | 45 + doc/troubleshooting.md | 59 + doc/usage/error_handling.md | 52 + doc/usage/event_handing.md | 105 + .../complete_error_handling_example.md | 115 + .../PaginatedRequestIterator.php | 253 + .../example_customers_history.php | 39 + .../example_customers_list.php | 47 + .../examples/complex_pagination/index.md | 8 + doc/usage/examples/create_order.md | 104 + doc/usage/examples/fetch_orders.md | 50 + doc/usage/examples/index.md | 7 + doc/usage/examples/orders_history.md | 34 + doc/usage/instantiation.md | 255 + doc/usage/sending_a_request.md | 144 + doc/usage/usage.md | 15 + lib/RetailCrm/ApiClient.php | 70 - lib/RetailCrm/Client/AbstractLoader.php | 174 - lib/RetailCrm/Client/ApiVersion3.php | 47 - lib/RetailCrm/Client/ApiVersion4.php | 51 - lib/RetailCrm/Client/ApiVersion5.php | 58 - lib/RetailCrm/Exception/CurlException.php | 24 - .../Exception/InvalidJsonException.php | 24 - lib/RetailCrm/Exception/LimitException.php | 24 - lib/RetailCrm/Exception/NotFoundException.php | 24 - lib/RetailCrm/Http/Client.php | 203 - lib/RetailCrm/Http/RequestOptions.php | 95 - lib/RetailCrm/Methods/V3/Customers.php | 206 - lib/RetailCrm/Methods/V3/Orders.php | 269 - lib/RetailCrm/Methods/V3/Packs.php | 197 - lib/RetailCrm/Methods/V3/References.php | 515 - lib/RetailCrm/Methods/V3/Statistic.php | 41 - lib/RetailCrm/Methods/V3/Stores.php | 86 - lib/RetailCrm/Methods/V3/Telephony.php | 168 - lib/RetailCrm/Methods/V4/Customers.php | 57 - lib/RetailCrm/Methods/V4/Delivery.php | 79 - lib/RetailCrm/Methods/V4/Marketplace.php | 51 - lib/RetailCrm/Methods/V4/Orders.php | 279 - lib/RetailCrm/Methods/V4/Packs.php | 27 - lib/RetailCrm/Methods/V4/References.php | 77 - lib/RetailCrm/Methods/V4/Settings.php | 183 - lib/RetailCrm/Methods/V4/Statistic.php | 27 - lib/RetailCrm/Methods/V4/Stores.php | 116 - lib/RetailCrm/Methods/V4/Telephony.php | 27 - lib/RetailCrm/Methods/V4/Users.php | 105 - lib/RetailCrm/Methods/V5/Costs.php | 216 - lib/RetailCrm/Methods/V5/CustomFields.php | 261 - lib/RetailCrm/Methods/V5/Customers.php | 144 - .../Methods/V5/CustomersCorporate.php | 656 -- lib/RetailCrm/Methods/V5/Delivery.php | 178 - lib/RetailCrm/Methods/V5/Files.php | 209 - .../Methods/V5/IntegrationPayments.php | 95 - lib/RetailCrm/Methods/V5/Module.php | 76 - lib/RetailCrm/Methods/V5/Orders.php | 153 - lib/RetailCrm/Methods/V5/Packs.php | 27 - lib/RetailCrm/Methods/V5/References.php | 287 - lib/RetailCrm/Methods/V5/Segments.php | 54 - lib/RetailCrm/Methods/V5/Settings.php | 38 - lib/RetailCrm/Methods/V5/Statistic.php | 27 - lib/RetailCrm/Methods/V5/Stores.php | 110 - lib/RetailCrm/Methods/V5/Tasks.php | 132 - lib/RetailCrm/Methods/V5/Telephony.php | 36 - lib/RetailCrm/Methods/V5/Users.php | 53 - lib/RetailCrm/Response/ApiResponse.php | 222 - models/.gitkeep | 0 phpcs.xml.dist | 13 + phpdoc.dist.xml | 21 + phpmd.xml | 47 + phpstan.neon | 5 + phpunit.xml.dist | 44 +- src/Builder/ClientBuilder.php | 492 + src/Builder/FilesystemCacheBuilder.php | 89 + src/Builder/FormEncoderBuilder.php | 120 + src/Client.php | 343 + .../AbstractModelsProcessorCommand.php | 54 + src/Command/ClearModelsCommand.php | 86 + src/Command/CompilerPromptCommand.php | 171 + src/Command/GenerateModelsCommand.php | 94 + src/Command/VerifyModelsCommand.php | 57 + src/Component/ComposerLocator.php | 83 + .../FilesIteratorChecksumGenerator.php | 130 + src/Component/FormData/FormEncoder.php | 131 + src/Component/FormData/Mapping/Accessor.php | 45 + src/Component/FormData/Mapping/JsonField.php | 26 + .../FormData/Mapping/NoTransform.php | 26 + .../FormData/Mapping/PostDeserialize.php | 26 + .../FormData/Mapping/PostSerialize.php | 26 + .../FormData/Mapping/SerializedName.php | 37 + src/Component/FormData/Mapping/Type.php | 37 + .../FormData/PropertyAnnotations.php | 73 + .../Encode/AbstractEncodeStrategy.php | 56 + .../Strategy/Encode/DateTimeStrategy.php | 34 + .../Encode/EncodeStrategyInterface.php | 44 + .../Strategy/Encode/EntityStrategy.php | 117 + .../Strategy/Encode/SimpleTypeStrategy.php | 86 + .../Encode/StreamInterfaceStrategy.php | 38 + .../Strategy/Encode/TypedArrayStrategy.php | 121 + .../FormData/Strategy/StrategyFactory.php | 159 + src/Component/ModelsGenerator.php | 235 + src/Component/PhpFilesIterator.php | 109 + .../Serializer/Annotation/AccessType.php | 21 + .../Serializer/Annotation/Accessor.php | 25 + .../Serializer/Annotation/AccessorOrder.php | 28 + .../Serializer/Annotation/Discriminator.php | 26 + .../Serializer/Annotation/Exclude.php | 19 + .../Serializer/Annotation/ExclusionPolicy.php | 54 + .../Serializer/Annotation/Expose.php | 19 + .../Serializer/Annotation/Groups.php | 17 + .../Serializer/Annotation/Inline.php | 15 + .../Serializer/Annotation/MaxDepth.php | 20 + .../Serializer/Annotation/PostDeserialize.php | 21 + .../Serializer/Annotation/PostSerialize.php | 15 + .../Serializer/Annotation/PreSerialize.php | 22 + .../Serializer/Annotation/ReadOnly.php | 19 + .../Serializer/Annotation/SerializedName.php | 35 + src/Component/Serializer/Annotation/Since.php | 15 + .../Serializer/Annotation/SkipWhenEmpty.php | 15 + src/Component/Serializer/Annotation/Type.php | 20 + src/Component/Serializer/Annotation/Until.php | 15 + .../Serializer/Annotation/Version.php | 21 + .../Serializer/Annotation/VirtualProperty.php | 57 + .../Serializer/ArraySupportDecorator.php | 227 + .../Exception/InvalidArgumentException.php | 19 + .../Serializer/Exception/InvalidNode.php | 24 + .../Serializer/Exception/RuntimeException.php | 27 + .../Serializer/Exception/SyntaxError.php | 24 + .../Generator/DeserializerGenerator.php | 463 + .../Generator/SerializerGenerator.php | 498 + .../Serializer/ModelsChecksumGenerator.php | 116 + .../Serializer/Parser/BaseJMSParser.php | 196 + src/Component/Serializer/Parser/JMSLexer.php | 132 + src/Component/Serializer/Parser/JMSParser.php | 564 ++ .../Serializer/Parser/JMSTypeParser.php | 142 + .../Serializer/Template/AbstractTemplate.php | 51 + .../Template/CustomDeserialization.php | 67 + .../Template/CustomSerialization.php | 61 + .../Serializer/Type/PropertyTypeMixed.php | 57 + .../Transformer/DateTimeTransformer.php | 123 + .../Transformer/RequestTransformer.php | 76 + .../Transformer/ResponseTransformer.php | 75 + src/Component/Utils.php | 79 + src/Enum/ByIdentifier.php | 22 + src/Enum/CacheDirectories.php | 22 + src/Enum/CombineTechnique.php | 24 + src/Enum/CountryCodeIso3166.php | 269 + src/Enum/Currency.php | 69 + .../CustomFields/CustomFieldDisplayArea.php | 28 + src/Enum/CustomFields/CustomFieldEntity.php | 23 + src/Enum/CustomFields/CustomFieldType.php | 28 + src/Enum/CustomFields/CustomFieldViewMode.php | 23 + .../CustomFieldViewModeMobile.php | 20 + src/Enum/Customers/AttachmentsStatus.php | 23 + src/Enum/Customers/ContragentType.php | 23 + src/Enum/Customers/CustomerType.php | 22 + src/Enum/Customers/Gender.php | 22 + src/Enum/Customers/TasksCountsStatus.php | 23 + src/Enum/Loyalty/AccountStatus.php | 23 + src/Enum/Loyalty/BonusOperationEventType.php | 22 + src/Enum/Loyalty/BonusOperationType.php | 28 + src/Enum/Loyalty/LoyaltyLevelType.php | 23 + src/Enum/Loyalty/PrivilegeType.php | 22 + src/Enum/NumericBoolean.php | 22 + src/Enum/Order/DeliveryState.php | 26 + src/Enum/Order/DiscountType.php | 27 + src/Enum/Order/PrivilegeType.php | 24 + src/Enum/PaginationLimit.php | 24 + src/Enum/Reference/CostGroupColor.php | 34 + src/Enum/Reference/StoreInventoryType.php | 22 + src/Enum/Reference/StoreType.php | 24 + src/Enum/RequestMethod.php | 29 + src/Enum/SiteAccess.php | 22 + src/Enum/Tasks/TaskStatus.php | 22 + src/Enum/Telephony/CallEventHangupStatus.php | 25 + src/Enum/Telephony/CallEventType.php | 23 + src/Enum/Telephony/CallResult.php | 26 + src/Enum/Users/UserStatus.php | 24 + src/Enum/YesNo.php | 22 + src/Event/AbstractRequestEvent.php | 117 + src/Event/FailureRequestEvent.php | 76 + src/Event/SuccessRequestEvent.php | 53 + src/Exception/Api/AccessDeniedException.php | 22 + .../Api/AccountDoesNotExistException.php | 22 + src/Exception/Api/ApiErrorException.php | 22 + .../Api/InvalidCredentialsException.php | 22 + .../Api/MissingCredentialsException.php | 22 + .../Api/MissingParameterException.php | 22 + src/Exception/Api/ValidationException.php | 22 + src/Exception/ApiException.php | 110 + src/Exception/Client/BuilderException.php | 50 + src/Exception/Client/HandlerException.php | 22 + src/Exception/Client/HttpClientException.php | 22 + src/Exception/ClientException.php | 30 + src/Factory/ApiExceptionFactory.php | 85 + src/Factory/ClientFactory.php | 335 + src/Factory/RequestPipelineFactory.php | 69 + src/Factory/ResponsePipelineFactory.php | 73 + src/Factory/SerializerFactory.php | 34 + src/Factory/SimpleClientFactory.php | 90 + src/Handler/AbstractHandler.php | 81 + .../Request/CallbackRequestHandler.php | 54 + .../GetParameterAuthenticatorHandler.php | 58 + .../Request/HeaderAuthenticatorHandler.php | 52 + src/Handler/Request/PsrRequestHandler.php | 84 + src/Handler/Request/RequestDataHandler.php | 86 + .../Response/AbstractResponseHandler.php | 114 + .../Response/AccountNotFoundHandler.php | 78 + .../Response/CallbackResponseHandler.php | 48 + src/Handler/Response/ErrorResponseHandler.php | 49 + .../Response/FilesDownloadResponseHandler.php | 93 + .../Response/UnmarshalResponseHandler.php | 36 + .../ApiExceptionFactoryAwareInterface.php | 26 + src/Interfaces/ApiExceptionInterface.php | 43 + src/Interfaces/AuthenticatorInterface.php | 30 + src/Interfaces/BuilderInterface.php | 27 + src/Interfaces/ClientExceptionInterface.php | 28 + src/Interfaces/ClientFactoryInterface.php | 32 + .../EventDispatcherAwareInterface.php | 28 + src/Interfaces/FormEncoderInterface.php | 49 + src/Interfaces/HandlerInterface.php | 62 + src/Interfaces/Orders/CustomerInterface.php | 20 + src/Interfaces/PsrFactoriesAwareInterface.php | 38 + src/Interfaces/RequestInterface.php | 20 + .../RequestTransformerInterface.php | 51 + src/Interfaces/ResponseInterface.php | 20 + .../ResponseTransformerInterface.php | 58 + src/Interfaces/SerializerAwareInterface.php | 31 + .../Callback/Entity/Delivery/Coordinates.php | 37 + .../Callback/Entity/Delivery/Customer.php | 346 + .../Entity/Delivery/DeliveryAddress.php | 189 + .../Callback/Entity/Delivery/Manager.php | 69 + .../Callback/Entity/Delivery/Package.php | 69 + .../Callback/Entity/Delivery/PackageItem.php | 125 + .../Callback/Entity/Delivery/PaymentType.php | 37 + .../RequestProperty/RequestCalculate.php | 110 + .../RequestProperty/RequestDelete.php | 29 + .../Delivery/RequestProperty/RequestPrint.php | 45 + .../Delivery/RequestProperty/RequestSave.php | 125 + .../RequestProperty/RequestShipmentDelete.php | 37 + .../RequestProperty/RequestShipmentSave.php | 94 + .../ResponseAutocompleteItem.php | 45 + .../ResponseProperty/ResponseCalculate.php | 109 + .../ResponseLoadDeliveryData.php | 118 + .../ResponseProperty/ResponseSave.php | 61 + .../ResponseProperty/ResponseShipmentSave.php | 37 + .../Entity/Delivery/SaveDeliveryData.php | 126 + .../SerializedStoreWeekOpeningHours.php | 126 + .../Entity/Delivery/ShipmentOrder.php | 37 + src/Model/Callback/Entity/Delivery/Store.php | 37 + .../Entity/Delivery/StoreWorkTime.php | 84 + src/Model/Callback/Entity/Delivery/Tariff.php | 53 + .../Callback/Entity/Delivery/Terminal.php | 93 + src/Model/Callback/Entity/Delivery/Unit.php | 45 + .../Entity/Integration/IntegrationModule.php | 37 + .../Entity/Payments/ModuleApiRequest.php | 37 + .../Callback/Entity/Payments/ModuleRefund.php | 53 + .../Entity/Payments/PaymentCreateResult.php | 45 + .../Entity/Store/InventoriesDataModel.php | 29 + .../Callback/Entity/Store/OrderDataModel.php | 45 + .../Callback/Entity/Store/PackDataModel.php | 86 + .../Callback/Entity/Store/PackItemModel.php | 37 + .../Callback/Enum/Recommendations/Mode.php | 22 + .../Delivery/AutocompleteResponse.php | 46 + .../Response/Delivery/CalculateResponse.php | 30 + .../Response/Delivery/GetResponse.php | 30 + .../Response/Delivery/SaveResponse.php | 30 + .../Delivery/ShipmentPointListResponse.php | 29 + .../Delivery/ShipmentSaveResponse.php | 30 + .../Response/Delivery/TariffListResponse.php | 29 + src/Model/Callback/Response/ErrorResponse.php | 22 + src/Model/Callback/Response/Payments/Item.php | 85 + .../Payments/PaymentCreateResponse.php | 30 + .../Payments/PaymentRefundResponse.php | 30 + .../Recommendation/RecommendationResponse.php | 37 + .../Store/InventoriesUploadResponse.php | 45 + .../Response/Store/ReserveResponse.php | 45 + src/Model/Entity/CodeValueModel.php | 37 + src/Model/Entity/Costs/Cost.php | 118 + src/Model/Entity/Costs/CostOrder.php | 45 + .../Entity/CustomFields/CustomDictionary.php | 45 + src/Model/Entity/CustomFields/CustomField.php | 125 + .../SerializedCustomDictionaryElement.php | 45 + src/Model/Entity/Customers/Customer.php | 346 + .../Entity/Customers/CustomerAddress.php | 205 + .../Entity/Customers/CustomerContragent.php | 142 + .../Entity/Customers/CustomerHistory.php | 126 + src/Model/Entity/Customers/CustomerNote.php | 62 + src/Model/Entity/Customers/CustomerPhone.php | 41 + src/Model/Entity/Customers/CustomerTag.php | 57 + src/Model/Entity/Customers/HistoryAddress.php | 53 + src/Model/Entity/Customers/Segment.php | 78 + .../Customers/SerializedCustomerReference.php | 39 + .../Entity/CustomersCorporate/Company.php | 158 + .../CustomersCorporate/CustomerContact.php | 53 + .../CustomerContactCompany.php | 29 + .../CustomersCorporate/CustomerCorporate.php | 255 + .../CustomerCorporateHistory.php | 142 + .../EntityWithExternalIdInput.php | 37 + .../EntityWithExternalIdNameOutput.php | 29 + .../SerializedEntityCustomer.php | 77 + .../SerializedRelationAbstractCustomer.php | 93 + .../Entity/Delivery/DeliveryCalculation.php | 53 + .../Entity/Delivery/DeliveryShipment.php | 126 + .../Delivery/RequestStatusUpdateItem.php | 61 + .../Entity/Delivery/SerializedEntityOrder.php | 104 + src/Model/Entity/Delivery/SerializedOrder.php | 69 + .../Delivery/SerializedOrderDelivery.php | 46 + .../Delivery/SerializedOrderProduct.php | 71 + src/Model/Entity/Delivery/StatusInfo.php | 46 + src/Model/Entity/Delivery/TimeInterval.php | 115 + src/Model/Entity/Files/Attachment.php | 37 + src/Model/Entity/Files/AttachmentCustomer.php | 45 + src/Model/Entity/Files/AttachmentOrder.php | 53 + src/Model/Entity/Files/File.php | 70 + src/Model/Entity/FixExternalRow.php | 54 + src/Model/Entity/HistoryApiKey.php | 29 + src/Model/Entity/HistoryUser.php | 29 + src/Model/Entity/IdModel.php | 29 + .../Integration/Bot/BotConfiguration.php | 53 + .../Delivery/DeliveryConfiguration.php | 159 + .../Delivery/DeliveryDataField.php | 109 + .../Integration/Delivery/PaymentType.php | 45 + .../Entity/Integration/Delivery/Plate.php | 45 + .../Entity/Integration/Delivery/Settings.php | 93 + .../Integration/Delivery/ShipmentPoint.php | 45 + .../Entity/Integration/Delivery/Status.php | 45 + .../Entity/Integration/IntegrationModule.php | 125 + .../Integration/IntegrationModuleEditInfo.php | 53 + src/Model/Entity/Integration/Integrations.php | 77 + .../Entity/Integration/Payment/Actions.php | 53 + .../Payment/PaymentConfiguration.php | 53 + src/Model/Entity/Integration/Payment/Shop.php | 67 + .../Integration/Recommendation/Mode.php | 37 + .../RecommendationConfiguration.php | 45 + src/Model/Entity/Integration/Store/Action.php | 45 + .../Integration/Store/StoreConfiguration.php | 29 + .../Telephony/SerializedAdditionalCodes.php | 37 + .../Telephony/SerializedExternalPhones.php | 37 + .../Telephony/TelephonyConfiguration.php | 85 + .../Transport/TransportConfiguration.php | 53 + src/Model/Entity/Loyalty/Customer.php | 45 + src/Model/Entity/Loyalty/Loyalty.php | 104 + src/Model/Entity/Loyalty/LoyaltyAccount.php | 134 + src/Model/Entity/Loyalty/LoyaltyBonus.php | 46 + .../Entity/Loyalty/LoyaltyCalculation.php | 69 + .../Entity/Loyalty/LoyaltyEventDiscount.php | 29 + src/Model/Entity/Loyalty/LoyaltyLevel.php | 61 + src/Model/Entity/Loyalty/Operation.php | 69 + src/Model/Entity/Loyalty/OperationBonus.php | 30 + src/Model/Entity/Loyalty/OperationEvent.php | 37 + src/Model/Entity/Loyalty/OperationOrder.php | 37 + src/Model/Entity/Loyalty/OrderProduct.php | 109 + .../Entity/Loyalty/OrderProductPriceItem.php | 37 + src/Model/Entity/Loyalty/PriceType.php | 41 + .../SerializedCreateLoyaltyAccount.php | 53 + .../Entity/Loyalty/SerializedLoyalty.php | 37 + .../Entity/Loyalty/SerializedLoyaltyOrder.php | 119 + src/Model/Entity/Loyalty/SerializedOrder.php | 71 + .../Loyalty/SerializedOrderDelivery.php | 41 + .../Entity/Loyalty/SerializedOrderProduct.php | 71 + .../Loyalty/SerializedOrderProductOffer.php | 103 + src/Model/Entity/Loyalty/SmsVerification.php | 62 + .../Entity/Orders/Delivery/CourierPhone.php | 22 + .../Entity/Orders/Delivery/DeliveryData.php | 674 ++ .../Orders/Delivery/OrderDeliveryAddress.php | 181 + src/Model/Entity/Orders/Delivery/Package.php | 69 + .../Entity/Orders/Delivery/PackageItem.php | 37 + .../Delivery/PackageItemOrderProduct.php | 45 + .../Delivery/SerializedDeliveryService.php | 45 + .../Delivery/SerializedOrderDelivery.php | 103 + .../Entity/Orders/Items/AbstractDiscount.php | 49 + .../Entity/Orders/Items/ItemProperty.php | 45 + src/Model/Entity/Orders/Items/Offer.php | 101 + .../Entity/Orders/Items/OrderProduct.php | 216 + .../Orders/Items/OrderProductPriceItem.php | 37 + src/Model/Entity/Orders/Items/PriceType.php | 41 + src/Model/Entity/Orders/Items/Unit.php | 67 + src/Model/Entity/Orders/MarketplaceData.php | 37 + src/Model/Entity/Orders/Order.php | 498 + src/Model/Entity/Orders/OrderContragent.php | 22 + src/Model/Entity/Orders/OrderHistory.php | 142 + src/Model/Entity/Orders/OrderStatusItem.php | 53 + src/Model/Entity/Orders/Payment.php | 78 + .../Entity/Orders/SerializedOrderLink.php | 37 + .../Orders/SerializedOrderReference.php | 41 + src/Model/Entity/Orders/SerializedPayment.php | 29 + .../Orders/SerializedRelationCustomer.php | 75 + src/Model/Entity/Packs/OrderProductPack.php | 102 + .../Entity/Packs/OrderProductPackHistory.php | 102 + src/Model/Entity/Pagination.php | 53 + src/Model/Entity/Payments/ApiCheckRequest.php | 45 + .../Payments/ApiCheckResponseResult.php | 37 + .../Payments/ApiCreateInvoiceRequest.php | 37 + .../ApiCreateInvoiceResponseResult.php | 29 + .../Payments/ApiUpdateInvoiceRequest.php | 109 + src/Model/Entity/Payments/ModuleRefund.php | 53 + src/Model/Entity/References/CostGroup.php | 61 + src/Model/Entity/References/CostItem.php | 93 + src/Model/Entity/References/Courier.php | 85 + .../Entity/References/DeliveryService.php | 45 + src/Model/Entity/References/DeliveryType.php | 175 + .../Entity/References/GeoHierarchyRow.php | 61 + src/Model/Entity/References/LegalEntity.php | 178 + src/Model/Entity/References/MGChannel.php | 61 + src/Model/Entity/References/OrderMethod.php | 69 + .../Entity/References/OrderProductStatus.php | 88 + src/Model/Entity/References/OrderType.php | 69 + src/Model/Entity/References/PaymentStatus.php | 93 + src/Model/Entity/References/PaymentType.php | 93 + src/Model/Entity/References/Point.php | 37 + src/Model/Entity/References/PriceType.php | 101 + .../Entity/References/SerializedUnit.php | 61 + src/Model/Entity/References/Site.php | 174 + src/Model/Entity/References/Status.php | 61 + src/Model/Entity/References/StatusGroup.php | 69 + src/Model/Entity/References/Store.php | 117 + src/Model/Entity/References/StoreAddress.php | 181 + src/Model/Entity/References/StorePhone.php | 22 + src/Model/Entity/Settings/Settings.php | 45 + src/Model/Entity/Settings/Value.php | 38 + src/Model/Entity/Source.php | 61 + src/Model/Entity/Store/Inventory.php | 83 + src/Model/Entity/Store/Offer.php | 54 + src/Model/Entity/Store/OfferPrice.php | 45 + src/Model/Entity/Store/PriceUploadInput.php | 61 + .../Store/PriceUploadNotFoundResponse.php | 45 + .../Entity/Store/PriceUploadPricesInput.php | 67 + src/Model/Entity/Store/Product.php | 190 + src/Model/Entity/Store/ProductGroup.php | 69 + src/Model/Entity/Store/ProductOffer.php | 173 + src/Model/Entity/Tasks/AbstractCustomer.php | 80 + src/Model/Entity/Tasks/Order.php | 53 + src/Model/Entity/Tasks/Task.php | 142 + src/Model/Entity/Telephony/Call.php | 134 + src/Model/Entity/Telephony/CallEvent.php | 93 + .../Entity/Telephony/SerializedCampaign.php | 37 + .../Entity/Telephony/SerializedCustomer.php | 77 + .../Entity/Telephony/SerializedLinks.php | 53 + .../Entity/Telephony/SerializedManager.php | 69 + .../Telephony/SerializedWebAnalyticsData.php | 37 + src/Model/Entity/Users/Group.php | 127 + src/Model/Entity/Users/User.php | 158 + .../Entity/Verification/SmsVerification.php | 62 + .../Verification/SmsVerificationConfirm.php | 37 + src/Model/Filter/Costs/CostFilter.php | 149 + .../CustomFields/CustomDictionaryFilter.php | 37 + .../Filter/CustomFields/CustomFieldFilter.php | 69 + src/Model/Filter/Customers/CustomerFilter.php | 505 + .../Customers/CustomerHistoryFilter.php | 62 + .../Filter/Customers/CustomerNoteFilter.php | 77 + .../CustomersCorporate/CompanyFilter.php | 37 + .../CustomerAddressFilter.php | 53 + .../CustomerContactFilter.php | 45 + .../CustomerCorporateFilter.php | 368 + .../ApiDeliveryShipmentFilterType.php | 93 + src/Model/Filter/Files/FileFilter.php | 125 + .../Loyalty/LoyaltyAccountApiFilterType.php | 181 + ...ltyAccountBonusOperationsApiFilterType.php | 39 + .../Filter/Loyalty/LoyaltyApiFilterType.php | 53 + src/Model/Filter/Orders/OrderFilter.php | 786 ++ .../Orders/OrderHistoryFilterV4Type.php | 62 + .../Filter/Packs/OrderProductPackFilter.php | 109 + .../OrderProductPackHistoryFilterType.php | 62 + .../Filter/Segments/SegmentApiFilter.php | 85 + .../Filter/Store/InventoryFilterType.php | 109 + src/Model/Filter/Store/ProductFilterType.php | 254 + .../Filter/Store/ProductGroupFilterType.php | 61 + .../Store/ProductPropertiesFilterType.php | 53 + src/Model/Filter/Store/ProductProperty.php | 61 + .../Filter/Store/ProductPropertyGroup.php | 37 + src/Model/Filter/Tasks/TaskFilter.php | 125 + src/Model/Filter/Users/ApiUserFilter.php | 102 + src/Model/Request/BySiteRequest.php | 55 + .../Request/Costs/CostsCreateRequest.php | 39 + .../Request/Costs/CostsDeleteRequest.php | 30 + src/Model/Request/Costs/CostsEditRequest.php | 20 + src/Model/Request/Costs/CostsRequest.php | 33 + .../Request/Costs/CostsUploadRequest.php | 30 + .../CustomDictionaryCreateRequest.php | 44 + .../CustomFieldsCreateRequest.php | 43 + .../CustomFieldsDictionariesRequest.php | 33 + .../CustomFields/CustomFieldsRequest.php | 33 + .../Customers/CustomersCombineRequest.php | 40 + .../Customers/CustomersCreateRequest.php | 39 + .../Customers/CustomersEditRequest.php | 29 + .../CustomersFixExternalIdsRequest.php | 31 + .../Customers/CustomersHistoryRequest.php | 33 + .../Customers/CustomersNotesCreateRequest.php | 39 + .../Customers/CustomersNotesRequest.php | 33 + .../Request/Customers/CustomersRequest.php | 33 + .../Customers/CustomersUploadRequest.php | 39 + ...stomersCorporateAddressesCreateRequest.php | 31 + ...CustomersCorporateAddressesEditRequest.php | 29 + .../CustomersCorporateAddressesRequest.php | 41 + ...stomersCorporateCompaniesCreateRequest.php | 31 + ...CustomersCorporateCompaniesEditRequest.php | 29 + .../CustomersCorporateCompaniesRequest.php | 41 + ...ustomersCorporateContactsCreateRequest.php | 31 + .../CustomersCorporateContactsEditRequest.php | 29 + .../CustomersCorporateContactsRequest.php | 41 + .../CustomersCorporateCreateRequest.php | 39 + .../CustomersCorporateEditRequest.php | 29 + ...ustomersCorporateFixExternalIdsRequest.php | 31 + .../CustomersCorporateRequest.php | 33 + .../CustomersCorporateUploadRequest.php | 39 + .../Delivery/DeliveryCalculateRequest.php | 40 + .../DeliveryShipmentsCreateRequest.php | 47 + .../Delivery/DeliveryShipmentsRequest.php | 33 + .../Request/Delivery/TrackingRequest.php | 31 + src/Model/Request/Files/FilesEditRequest.php | 31 + src/Model/Request/Files/FilesRequest.php | 33 + .../Request/Files/FilesUploadRequest.php | 41 + .../IntegrationModulesEditRequest.php | 42 + .../Request/Loyalty/LoyaltiesRequest.php | 33 + .../Loyalty/LoyaltyAccountCreateRequest.php | 39 + .../Loyalty/LoyaltyAccountEditRequest.php | 42 + .../Loyalty/LoyaltyAccountsRequest.php | 33 + .../Loyalty/LoyaltyBonusCreditRequest.php | 55 + .../Loyalty/LoyaltyBonusOperationsRequest.php | 33 + .../Loyalty/LoyaltyCalculateRequest.php | 47 + .../Request/Orders/OrdersCombineRequest.php | 48 + .../Request/Orders/OrdersCreateRequest.php | 39 + .../Request/Orders/OrdersEditRequest.php | 37 + .../Orders/OrdersFixExternalIdsRequest.php | 41 + .../Request/Orders/OrdersHistoryRequest.php | 33 + .../Orders/OrdersLinksCreateRequest.php | 39 + .../Orders/OrdersLoyaltyApplyRequest.php | 47 + .../Orders/OrdersPaymentsCreateRequest.php | 39 + .../Orders/OrdersPaymentsEditRequest.php | 29 + src/Model/Request/Orders/OrdersRequest.php | 33 + .../Request/Orders/OrdersStatusesRequest.php | 38 + .../Request/Orders/OrdersUploadRequest.php | 39 + .../Request/Packs/PacksCreateRequest.php | 44 + .../Request/Packs/PacksHistoryRequest.php | 33 + src/Model/Request/Packs/PacksRequest.php | 33 + .../Request/Payments/PaymentCheckRequest.php | 44 + .../Payments/PaymentCreateInvoiceRequest.php | 44 + .../Payments/PaymentUpdateInvoiceRequest.php | 44 + .../References/CostGroupsEditRequest.php | 44 + .../References/CostItemsEditRequest.php | 44 + .../References/CouriersCreateRequest.php | 44 + .../DeliveryServicesEditRequest.php | 44 + .../References/DeliveryTypesEditRequest.php | 44 + .../References/LegalEntityEditRequest.php | 44 + .../References/OrderMethodsEditRequest.php | 44 + .../References/OrderTypesEditRequest.php | 44 + .../References/PaymentStatusesEditRequest.php | 44 + .../References/PaymentTypesEditRequest.php | 44 + .../References/PriceTypesEditRequest.php | 44 + .../References/ProductStatusesEditRequest.php | 44 + .../Request/References/SitesEditRequest.php | 44 + .../References/StatusesEditRequest.php | 44 + .../Request/References/StoresEditRequest.php | 44 + .../Request/References/UnitsEditRequest.php | 44 + .../Request/Segments/SegmentsRequest.php | 33 + .../Request/Store/InventoriesRequest.php | 33 + .../Store/InventoriesUploadRequest.php | 39 + .../Request/Store/PricesUploadRequest.php | 43 + .../Request/Store/ProductGroupsRequest.php | 33 + .../Store/ProductPropertiesRequest.php | 30 + src/Model/Request/Store/ProductsRequest.php | 33 + .../Request/Tasks/TasksCreateRequest.php | 39 + src/Model/Request/Tasks/TasksRequest.php | 33 + .../Telephony/TelephonyCallEventRequest.php | 44 + .../Telephony/TelephonyCallsUploadRequest.php | 43 + .../Telephony/TelephonyManagerRequest.php | 46 + .../Request/Traits/HistoryPageLimitTrait.php | 29 + src/Model/Request/Traits/PageLimitTrait.php | 37 + src/Model/Request/Users/UserGroupsRequest.php | 24 + src/Model/Request/Users/UsersRequest.php | 33 + .../Request/Users/UsersStatusRequest.php | 42 + .../SmsVerificationConfirmRequest.php | 44 + src/Model/RequestData.php | 47 + .../Response/AbstractPaginatedResponse.php | 32 + .../Response/Api/ApiVersionsResponse.php | 30 + src/Model/Response/Api/Credentials.php | 46 + .../Response/Costs/CostsDeleteResponse.php | 38 + src/Model/Response/Costs/CostsGetResponse.php | 30 + src/Model/Response/Costs/CostsResponse.php | 30 + .../Response/Costs/CostsUploadResponse.php | 30 + .../CustomDictionaryCreateResponse.php | 30 + .../CustomDictionaryGetResponse.php | 30 + .../CustomFieldsCreateResponse.php | 30 + .../CustomFieldsDictionariesResponse.php | 30 + .../CustomFields/CustomFieldsEditResponse.php | 20 + .../CustomFields/CustomFieldsGetResponse.php | 30 + .../CustomFields/CustomFieldsResponse.php | 30 + .../Customers/CustomerNotesResponse.php | 30 + .../Customers/CustomersEditResponse.php | 30 + .../Customers/CustomersGetResponse.php | 30 + .../Customers/CustomersHistoryResponse.php | 39 + .../Response/Customers/CustomersResponse.php | 30 + .../Customers/CustomersUploadResponse.php | 30 + .../CustomersCorporateAddressesResponse.php | 30 + .../CustomersCorporateCompaniesResponse.php | 30 + .../CustomersCorporateContactsResponse.php | 30 + .../CustomersCorporateGetResponse.php | 30 + .../CustomersCorporateHistoryResponse.php | 39 + .../CustomersCorporateResponse.php | 30 + .../Delivery/DeliveryCalculateResponse.php | 30 + .../DeliveryShipmentsCreateResponse.php | 30 + .../Delivery/DeliveryShipmentsGetResponse.php | 30 + .../Delivery/DeliveryShipmentsResponse.php | 30 + src/Model/Response/ErrorResponse.php | 45 + .../Response/Files/FilesDownloadResponse.php | 40 + src/Model/Response/Files/FilesGetResponse.php | 30 + src/Model/Response/Files/FilesResponse.php | 30 + .../Response/Files/FilesUploadResponse.php | 30 + src/Model/Response/IdResponse.php | 29 + .../IntegrationModulesEditResponse.php | 31 + .../IntegrationModulesGetResponse.php | 30 + .../Response/Loyalty/LoyaltiesResponse.php | 30 + .../LoyaltyAccountActivateResponse.php | 38 + .../Loyalty/LoyaltyAccountCreateResponse.php | 46 + .../Loyalty/LoyaltyAccountsResponse.php | 30 + .../Loyalty/LoyaltyBonusCreditResponse.php | 30 + .../LoyaltyBonusOperationsResponse.php | 30 + .../Loyalty/LoyaltyCalculateResponse.php | 46 + .../Response/Orders/OrdersCombineResponse.php | 30 + .../Response/Orders/OrdersCreateResponse.php | 30 + .../Response/Orders/OrdersGetResponse.php | 30 + .../Response/Orders/OrdersHistoryResponse.php | 39 + .../Orders/OrdersLoyaltyApplyResponse.php | 38 + src/Model/Response/Orders/OrdersResponse.php | 30 + .../Orders/OrdersStatusesResponse.php | 30 + .../Response/Orders/OrdersUploadResponse.php | 46 + src/Model/Response/Packs/PacksGetResponse.php | 30 + .../Response/Packs/PacksHistoryResponse.php | 39 + src/Model/Response/Packs/PacksResponse.php | 30 + .../Payments/PaymentCheckResponse.php | 30 + .../Payments/PaymentCreateInvoiceResponse.php | 30 + .../References/CostGroupsResponse.php | 30 + .../Response/References/CostItemsResponse.php | 30 + .../Response/References/CountriesResponse.php | 30 + .../Response/References/CouriersResponse.php | 30 + .../References/DeliveryServicesResponse.php | 30 + .../References/DeliveryTypesResponse.php | 30 + .../References/LegalEntitiesResponse.php | 30 + .../References/MgChannelsResponse.php | 30 + .../References/OrderMethodsResponse.php | 30 + .../References/OrderTypesResponse.php | 30 + .../References/PaymentStatusesResponse.php | 30 + .../References/PaymentTypesResponse.php | 30 + .../References/PriceTypesResponse.php | 30 + .../References/ProductStatusesResponse.php | 30 + .../Response/References/SitesResponse.php | 30 + .../References/StatusGroupsResponse.php | 30 + .../Response/References/StatusesResponse.php | 30 + .../Response/References/StoresResponse.php | 30 + .../Response/References/UnitsResponse.php | 30 + .../Response/Segments/SegmentsResponse.php | 30 + .../Response/Settings/SettingsResponse.php | 30 + .../Response/Store/InventoriesResponse.php | 30 + .../Store/InventoriesUploadResponse.php | 38 + .../Response/Store/PricesUploadResponse.php | 38 + .../Response/Store/ProductGroupsResponse.php | 30 + .../Store/ProductPropertiesResponse.php | 30 + src/Model/Response/Store/ProductsResponse.php | 30 + src/Model/Response/SuccessResponse.php | 32 + src/Model/Response/Tasks/TasksGetResponse.php | 30 + src/Model/Response/Tasks/TasksResponse.php | 30 + .../Response/Telephony/CallEventResponse.php | 38 + .../Telephony/CallsUploadResponse.php | 38 + .../Response/Telephony/ManagerResponse.php | 46 + .../Response/Users/UserGroupsResponse.php | 30 + src/Model/Response/Users/UsersGetResponse.php | 30 + src/Model/Response/Users/UsersResponse.php | 30 + .../SmsVerificationConfirmResponse.php | 30 + .../SmsVerificationStatusResponse.php | 20 + src/Model/ResponseData.php | 54 + .../AbstractApiResourceGroup.php | 184 + src/ResourceGroup/Api.php | 163 + src/ResourceGroup/Costs.php | 487 + src/ResourceGroup/CustomFields.php | 566 ++ src/ResourceGroup/Customers.php | 785 ++ src/ResourceGroup/CustomersCorporate.php | 1428 +++ src/ResourceGroup/Delivery.php | 454 + src/ResourceGroup/Files.php | 373 + src/ResourceGroup/Integration.php | 151 + src/ResourceGroup/Loyalty.php | 549 ++ src/ResourceGroup/Orders.php | 1047 ++ src/ResourceGroup/Packs.php | 382 + src/ResourceGroup/Payments.php | 208 + src/ResourceGroup/References.php | 2105 ++++ src/ResourceGroup/Segments.php | 85 + src/ResourceGroup/Settings.php | 74 + src/ResourceGroup/Statistics.php | 70 + src/ResourceGroup/Store.php | 416 + src/ResourceGroup/Tasks.php | 267 + src/ResourceGroup/Telephony.php | 224 + src/ResourceGroup/Users.php | 259 + src/ResourceGroup/Verification.php | 138 + src/Traits/ApiExceptionFactoryAwareTrait.php | 35 + src/Traits/EventDispatcherAwareTrait.php | 49 + src/Traits/PsrFactoriesAwareTrait.php | 65 + src/Traits/SerializerAwareTrait.php | 43 + tests/RetailCrm/Test/TestCase.php | 95 - tests/RetailCrm/Tests/ApiClientTest.php | 33 - tests/RetailCrm/Tests/Http/ClientTest.php | 108 - .../Tests/Http/RequestOptionsTest.php | 25 - .../Tests/Methods/CommonMethodsTest.php | 77 - .../Version4/ApiClientCustomersTest.php | 281 - .../Version4/ApiClientMarketplaceTest.php | 47 - .../Methods/Version4/ApiClientOrdersTest.php | 327 - .../Methods/Version4/ApiClientPacksTest.php | 62 - .../Methods/Version4/ApiClientPricesTest.php | 108 - .../Version4/ApiClientReferenceTest.php | 168 - .../Methods/Version4/ApiClientStoreTest.php | 141 - .../Version4/ApiClientTelephonyTest.php | 185 - .../Methods/Version4/ApiClientUsersTest.php | 63 - .../ApiClientCustomersCorporateTest.php | 666 -- .../Version5/ApiClientCustomersTest.php | 444 - .../Version5/ApiClientDeliveryTest.php | 106 - .../Methods/Version5/ApiClientFilesTest.php | 87 - .../Version5/ApiClientMarketplaceTest.php | 57 - .../Methods/Version5/ApiClientOrdersTest.php | 420 - .../Methods/Version5/ApiClientPacksTest.php | 63 - .../Methods/Version5/ApiClientPricesTest.php | 107 - .../Version5/ApiClientReferenceTest.php | 207 - .../Methods/Version5/ApiClientStoreTest.php | 170 - .../Methods/Version5/ApiClientTasksTest.php | 78 - .../Version5/ApiClientTelephonyTest.php | 118 - .../Methods/Version5/ApiClientUsersTest.php | 75 - tests/RetailCrm/Tests/Resources/Report.pdf | Bin 124225 -> 0 bytes .../Tests/Response/ApiResponseTest.php | 283 - tests/bootstrap.php | 22 +- tests/src/Builder/ClientBuilderTest.php | 96 + tests/src/ClientTest.php | 124 + tests/src/Command/ClearModelsCommandTest.php | 32 + .../src/Command/CompilerPromptCommandTest.php | 70 + .../src/Command/GenerateModelsCommandTest.php | 31 + tests/src/Command/VerifyModelsCommandTest.php | 31 + tests/src/Component/ComposerLocatorTest.php | 38 + .../FilesIteratorChecksumGeneratorTest.php | 40 + tests/src/Component/PhpFilesIteratorTest.php | 34 + .../ModelsChecksumGeneratorTest.php | 31 + tests/src/Exception/ClientExceptionTest.php | 30 + tests/src/Factory/ApiExceptionFactoryTest.php | 129 + tests/src/Factory/ClientFactoryTest.php | 196 + tests/src/Factory/SimpleClientFactoryTest.php | 66 + .../Request/CallbackRequestHandlerTest.php | 39 + .../Response/CallbackResponseHandlerTest.php | 43 + .../Callback/Entity/Delivery/CustomerTest.php | 33 + .../AbstractApiResourceGroupTest.php | 252 + tests/src/ResourceGroup/ApiTest.php | 78 + tests/src/ResourceGroup/CostsTest.php | 278 + tests/src/ResourceGroup/CustomFieldsTest.php | 346 + .../ResourceGroup/CustomersCorporateTest.php | 1109 +++ tests/src/ResourceGroup/CustomersTest.php | 2699 +++++ tests/src/ResourceGroup/DeliveryTest.php | 327 + tests/src/ResourceGroup/ErrorTest.php | 103 + tests/src/ResourceGroup/FilesTest.php | 202 + tests/src/ResourceGroup/IntegrationTests.php | 157 + tests/src/ResourceGroup/LoyaltyTest.php | 734 ++ tests/src/ResourceGroup/OrdersTest.php | 8751 +++++++++++++++++ tests/src/ResourceGroup/PacksTest.php | 1358 +++ tests/src/ResourceGroup/PaymentsTest.php | 116 + tests/src/ResourceGroup/ReferencesTest.php | 8503 ++++++++++++++++ tests/src/ResourceGroup/SegmentsTest.php | 122 + tests/src/ResourceGroup/SettingsTest.php | 56 + tests/src/ResourceGroup/StatisticsTest.php | 42 + tests/src/ResourceGroup/StoreTest.php | 621 ++ tests/src/ResourceGroup/TasksTest.php | 178 + tests/src/ResourceGroup/TelephonyTest.php | 145 + tests/src/ResourceGroup/UsersTest.php | 259 + tests/src/ResourceGroup/VerificationTest.php | 81 + tests/utils/ArrayLogger.php | 60 + tests/utils/ClientFactoryDependentService.php | 79 + tests/utils/Exception/MatcherException.php | 34 + tests/utils/Factory/TestClientFactory.php | 68 + tests/utils/ReflectionUtils.php | 62 + .../AbstractApiResourceGroupTestCase.php | 330 + tests/utils/TestCase/ClientTestCase.php | 111 + tests/utils/TestConfig.php | 50 + 788 files changed, 79633 insertions(+), 11006 deletions(-) create mode 100644 .editorconfig create mode 100644 .env.dist create mode 100644 .github/workflows/code_quality.yml create mode 100644 .github/workflows/documentation.yml create mode 100755 bin/retailcrm-client create mode 100644 doc/compilation_prompt.md create mode 100644 doc/customization/customization.md create mode 100644 doc/customization/different_psr_implementations.md create mode 100644 doc/customization/pipelines/implementing_a_handler.md create mode 100644 doc/customization/pipelines/using_a_predefined_handler.md create mode 100644 doc/index.md create mode 100644 doc/structure.md create mode 100644 doc/troubleshooting.md create mode 100644 doc/usage/error_handling.md create mode 100644 doc/usage/event_handing.md create mode 100644 doc/usage/examples/complete_error_handling_example.md create mode 100644 doc/usage/examples/complex_pagination/PaginatedRequestIterator.php create mode 100644 doc/usage/examples/complex_pagination/example_customers_history.php create mode 100644 doc/usage/examples/complex_pagination/example_customers_list.php create mode 100644 doc/usage/examples/complex_pagination/index.md create mode 100644 doc/usage/examples/create_order.md create mode 100644 doc/usage/examples/fetch_orders.md create mode 100644 doc/usage/examples/index.md create mode 100644 doc/usage/examples/orders_history.md create mode 100644 doc/usage/instantiation.md create mode 100644 doc/usage/sending_a_request.md create mode 100644 doc/usage/usage.md delete mode 100644 lib/RetailCrm/ApiClient.php delete mode 100755 lib/RetailCrm/Client/AbstractLoader.php delete mode 100644 lib/RetailCrm/Client/ApiVersion3.php delete mode 100644 lib/RetailCrm/Client/ApiVersion4.php delete mode 100644 lib/RetailCrm/Client/ApiVersion5.php delete mode 100644 lib/RetailCrm/Exception/CurlException.php delete mode 100644 lib/RetailCrm/Exception/InvalidJsonException.php delete mode 100644 lib/RetailCrm/Exception/LimitException.php delete mode 100644 lib/RetailCrm/Exception/NotFoundException.php delete mode 100755 lib/RetailCrm/Http/Client.php delete mode 100644 lib/RetailCrm/Http/RequestOptions.php delete mode 100644 lib/RetailCrm/Methods/V3/Customers.php delete mode 100644 lib/RetailCrm/Methods/V3/Orders.php delete mode 100644 lib/RetailCrm/Methods/V3/Packs.php delete mode 100644 lib/RetailCrm/Methods/V3/References.php delete mode 100644 lib/RetailCrm/Methods/V3/Statistic.php delete mode 100644 lib/RetailCrm/Methods/V3/Stores.php delete mode 100644 lib/RetailCrm/Methods/V3/Telephony.php delete mode 100644 lib/RetailCrm/Methods/V4/Customers.php delete mode 100644 lib/RetailCrm/Methods/V4/Delivery.php delete mode 100644 lib/RetailCrm/Methods/V4/Marketplace.php delete mode 100644 lib/RetailCrm/Methods/V4/Orders.php delete mode 100644 lib/RetailCrm/Methods/V4/Packs.php delete mode 100644 lib/RetailCrm/Methods/V4/References.php delete mode 100644 lib/RetailCrm/Methods/V4/Settings.php delete mode 100644 lib/RetailCrm/Methods/V4/Statistic.php delete mode 100644 lib/RetailCrm/Methods/V4/Stores.php delete mode 100644 lib/RetailCrm/Methods/V4/Telephony.php delete mode 100644 lib/RetailCrm/Methods/V4/Users.php delete mode 100644 lib/RetailCrm/Methods/V5/Costs.php delete mode 100644 lib/RetailCrm/Methods/V5/CustomFields.php delete mode 100644 lib/RetailCrm/Methods/V5/Customers.php delete mode 100644 lib/RetailCrm/Methods/V5/CustomersCorporate.php delete mode 100644 lib/RetailCrm/Methods/V5/Delivery.php delete mode 100644 lib/RetailCrm/Methods/V5/Files.php delete mode 100644 lib/RetailCrm/Methods/V5/IntegrationPayments.php delete mode 100644 lib/RetailCrm/Methods/V5/Module.php delete mode 100644 lib/RetailCrm/Methods/V5/Orders.php delete mode 100644 lib/RetailCrm/Methods/V5/Packs.php delete mode 100644 lib/RetailCrm/Methods/V5/References.php delete mode 100644 lib/RetailCrm/Methods/V5/Segments.php delete mode 100644 lib/RetailCrm/Methods/V5/Settings.php delete mode 100644 lib/RetailCrm/Methods/V5/Statistic.php delete mode 100644 lib/RetailCrm/Methods/V5/Stores.php delete mode 100644 lib/RetailCrm/Methods/V5/Tasks.php delete mode 100644 lib/RetailCrm/Methods/V5/Telephony.php delete mode 100644 lib/RetailCrm/Methods/V5/Users.php delete mode 100644 lib/RetailCrm/Response/ApiResponse.php create mode 100644 models/.gitkeep create mode 100644 phpcs.xml.dist create mode 100644 phpdoc.dist.xml create mode 100644 phpmd.xml create mode 100644 phpstan.neon create mode 100644 src/Builder/ClientBuilder.php create mode 100644 src/Builder/FilesystemCacheBuilder.php create mode 100644 src/Builder/FormEncoderBuilder.php create mode 100644 src/Client.php create mode 100644 src/Command/AbstractModelsProcessorCommand.php create mode 100644 src/Command/ClearModelsCommand.php create mode 100644 src/Command/CompilerPromptCommand.php create mode 100644 src/Command/GenerateModelsCommand.php create mode 100644 src/Command/VerifyModelsCommand.php create mode 100644 src/Component/ComposerLocator.php create mode 100644 src/Component/FilesIteratorChecksumGenerator.php create mode 100644 src/Component/FormData/FormEncoder.php create mode 100644 src/Component/FormData/Mapping/Accessor.php create mode 100644 src/Component/FormData/Mapping/JsonField.php create mode 100644 src/Component/FormData/Mapping/NoTransform.php create mode 100644 src/Component/FormData/Mapping/PostDeserialize.php create mode 100644 src/Component/FormData/Mapping/PostSerialize.php create mode 100644 src/Component/FormData/Mapping/SerializedName.php create mode 100644 src/Component/FormData/Mapping/Type.php create mode 100644 src/Component/FormData/PropertyAnnotations.php create mode 100644 src/Component/FormData/Strategy/Encode/AbstractEncodeStrategy.php create mode 100644 src/Component/FormData/Strategy/Encode/DateTimeStrategy.php create mode 100644 src/Component/FormData/Strategy/Encode/EncodeStrategyInterface.php create mode 100644 src/Component/FormData/Strategy/Encode/EntityStrategy.php create mode 100644 src/Component/FormData/Strategy/Encode/SimpleTypeStrategy.php create mode 100644 src/Component/FormData/Strategy/Encode/StreamInterfaceStrategy.php create mode 100644 src/Component/FormData/Strategy/Encode/TypedArrayStrategy.php create mode 100644 src/Component/FormData/Strategy/StrategyFactory.php create mode 100644 src/Component/ModelsGenerator.php create mode 100644 src/Component/PhpFilesIterator.php create mode 100644 src/Component/Serializer/Annotation/AccessType.php create mode 100644 src/Component/Serializer/Annotation/Accessor.php create mode 100644 src/Component/Serializer/Annotation/AccessorOrder.php create mode 100644 src/Component/Serializer/Annotation/Discriminator.php create mode 100644 src/Component/Serializer/Annotation/Exclude.php create mode 100644 src/Component/Serializer/Annotation/ExclusionPolicy.php create mode 100644 src/Component/Serializer/Annotation/Expose.php create mode 100644 src/Component/Serializer/Annotation/Groups.php create mode 100644 src/Component/Serializer/Annotation/Inline.php create mode 100644 src/Component/Serializer/Annotation/MaxDepth.php create mode 100644 src/Component/Serializer/Annotation/PostDeserialize.php create mode 100644 src/Component/Serializer/Annotation/PostSerialize.php create mode 100644 src/Component/Serializer/Annotation/PreSerialize.php create mode 100644 src/Component/Serializer/Annotation/ReadOnly.php create mode 100644 src/Component/Serializer/Annotation/SerializedName.php create mode 100644 src/Component/Serializer/Annotation/Since.php create mode 100644 src/Component/Serializer/Annotation/SkipWhenEmpty.php create mode 100644 src/Component/Serializer/Annotation/Type.php create mode 100644 src/Component/Serializer/Annotation/Until.php create mode 100644 src/Component/Serializer/Annotation/Version.php create mode 100644 src/Component/Serializer/Annotation/VirtualProperty.php create mode 100644 src/Component/Serializer/ArraySupportDecorator.php create mode 100644 src/Component/Serializer/Exception/InvalidArgumentException.php create mode 100644 src/Component/Serializer/Exception/InvalidNode.php create mode 100644 src/Component/Serializer/Exception/RuntimeException.php create mode 100644 src/Component/Serializer/Exception/SyntaxError.php create mode 100644 src/Component/Serializer/Generator/DeserializerGenerator.php create mode 100644 src/Component/Serializer/Generator/SerializerGenerator.php create mode 100644 src/Component/Serializer/ModelsChecksumGenerator.php create mode 100644 src/Component/Serializer/Parser/BaseJMSParser.php create mode 100644 src/Component/Serializer/Parser/JMSLexer.php create mode 100644 src/Component/Serializer/Parser/JMSParser.php create mode 100644 src/Component/Serializer/Parser/JMSTypeParser.php create mode 100644 src/Component/Serializer/Template/AbstractTemplate.php create mode 100644 src/Component/Serializer/Template/CustomDeserialization.php create mode 100644 src/Component/Serializer/Template/CustomSerialization.php create mode 100644 src/Component/Serializer/Type/PropertyTypeMixed.php create mode 100644 src/Component/Transformer/DateTimeTransformer.php create mode 100644 src/Component/Transformer/RequestTransformer.php create mode 100644 src/Component/Transformer/ResponseTransformer.php create mode 100644 src/Component/Utils.php create mode 100644 src/Enum/ByIdentifier.php create mode 100644 src/Enum/CacheDirectories.php create mode 100644 src/Enum/CombineTechnique.php create mode 100644 src/Enum/CountryCodeIso3166.php create mode 100644 src/Enum/Currency.php create mode 100644 src/Enum/CustomFields/CustomFieldDisplayArea.php create mode 100644 src/Enum/CustomFields/CustomFieldEntity.php create mode 100644 src/Enum/CustomFields/CustomFieldType.php create mode 100644 src/Enum/CustomFields/CustomFieldViewMode.php create mode 100644 src/Enum/CustomFields/CustomFieldViewModeMobile.php create mode 100644 src/Enum/Customers/AttachmentsStatus.php create mode 100644 src/Enum/Customers/ContragentType.php create mode 100644 src/Enum/Customers/CustomerType.php create mode 100644 src/Enum/Customers/Gender.php create mode 100644 src/Enum/Customers/TasksCountsStatus.php create mode 100644 src/Enum/Loyalty/AccountStatus.php create mode 100644 src/Enum/Loyalty/BonusOperationEventType.php create mode 100644 src/Enum/Loyalty/BonusOperationType.php create mode 100644 src/Enum/Loyalty/LoyaltyLevelType.php create mode 100644 src/Enum/Loyalty/PrivilegeType.php create mode 100644 src/Enum/NumericBoolean.php create mode 100644 src/Enum/Order/DeliveryState.php create mode 100644 src/Enum/Order/DiscountType.php create mode 100644 src/Enum/Order/PrivilegeType.php create mode 100644 src/Enum/PaginationLimit.php create mode 100644 src/Enum/Reference/CostGroupColor.php create mode 100644 src/Enum/Reference/StoreInventoryType.php create mode 100644 src/Enum/Reference/StoreType.php create mode 100644 src/Enum/RequestMethod.php create mode 100644 src/Enum/SiteAccess.php create mode 100644 src/Enum/Tasks/TaskStatus.php create mode 100644 src/Enum/Telephony/CallEventHangupStatus.php create mode 100644 src/Enum/Telephony/CallEventType.php create mode 100644 src/Enum/Telephony/CallResult.php create mode 100644 src/Enum/Users/UserStatus.php create mode 100644 src/Enum/YesNo.php create mode 100644 src/Event/AbstractRequestEvent.php create mode 100644 src/Event/FailureRequestEvent.php create mode 100644 src/Event/SuccessRequestEvent.php create mode 100644 src/Exception/Api/AccessDeniedException.php create mode 100644 src/Exception/Api/AccountDoesNotExistException.php create mode 100644 src/Exception/Api/ApiErrorException.php create mode 100644 src/Exception/Api/InvalidCredentialsException.php create mode 100644 src/Exception/Api/MissingCredentialsException.php create mode 100644 src/Exception/Api/MissingParameterException.php create mode 100644 src/Exception/Api/ValidationException.php create mode 100644 src/Exception/ApiException.php create mode 100644 src/Exception/Client/BuilderException.php create mode 100644 src/Exception/Client/HandlerException.php create mode 100644 src/Exception/Client/HttpClientException.php create mode 100644 src/Exception/ClientException.php create mode 100644 src/Factory/ApiExceptionFactory.php create mode 100644 src/Factory/ClientFactory.php create mode 100644 src/Factory/RequestPipelineFactory.php create mode 100644 src/Factory/ResponsePipelineFactory.php create mode 100644 src/Factory/SerializerFactory.php create mode 100644 src/Factory/SimpleClientFactory.php create mode 100644 src/Handler/AbstractHandler.php create mode 100644 src/Handler/Request/CallbackRequestHandler.php create mode 100644 src/Handler/Request/GetParameterAuthenticatorHandler.php create mode 100644 src/Handler/Request/HeaderAuthenticatorHandler.php create mode 100644 src/Handler/Request/PsrRequestHandler.php create mode 100644 src/Handler/Request/RequestDataHandler.php create mode 100644 src/Handler/Response/AbstractResponseHandler.php create mode 100644 src/Handler/Response/AccountNotFoundHandler.php create mode 100644 src/Handler/Response/CallbackResponseHandler.php create mode 100644 src/Handler/Response/ErrorResponseHandler.php create mode 100644 src/Handler/Response/FilesDownloadResponseHandler.php create mode 100644 src/Handler/Response/UnmarshalResponseHandler.php create mode 100644 src/Interfaces/ApiExceptionFactoryAwareInterface.php create mode 100644 src/Interfaces/ApiExceptionInterface.php create mode 100644 src/Interfaces/AuthenticatorInterface.php create mode 100644 src/Interfaces/BuilderInterface.php create mode 100644 src/Interfaces/ClientExceptionInterface.php create mode 100644 src/Interfaces/ClientFactoryInterface.php create mode 100644 src/Interfaces/EventDispatcherAwareInterface.php create mode 100644 src/Interfaces/FormEncoderInterface.php create mode 100644 src/Interfaces/HandlerInterface.php create mode 100644 src/Interfaces/Orders/CustomerInterface.php create mode 100644 src/Interfaces/PsrFactoriesAwareInterface.php create mode 100644 src/Interfaces/RequestInterface.php create mode 100644 src/Interfaces/RequestTransformerInterface.php create mode 100644 src/Interfaces/ResponseInterface.php create mode 100644 src/Interfaces/ResponseTransformerInterface.php create mode 100644 src/Interfaces/SerializerAwareInterface.php create mode 100644 src/Model/Callback/Entity/Delivery/Coordinates.php create mode 100644 src/Model/Callback/Entity/Delivery/Customer.php create mode 100644 src/Model/Callback/Entity/Delivery/DeliveryAddress.php create mode 100644 src/Model/Callback/Entity/Delivery/Manager.php create mode 100644 src/Model/Callback/Entity/Delivery/Package.php create mode 100644 src/Model/Callback/Entity/Delivery/PackageItem.php create mode 100644 src/Model/Callback/Entity/Delivery/PaymentType.php create mode 100644 src/Model/Callback/Entity/Delivery/RequestProperty/RequestCalculate.php create mode 100644 src/Model/Callback/Entity/Delivery/RequestProperty/RequestDelete.php create mode 100644 src/Model/Callback/Entity/Delivery/RequestProperty/RequestPrint.php create mode 100644 src/Model/Callback/Entity/Delivery/RequestProperty/RequestSave.php create mode 100644 src/Model/Callback/Entity/Delivery/RequestProperty/RequestShipmentDelete.php create mode 100644 src/Model/Callback/Entity/Delivery/RequestProperty/RequestShipmentSave.php create mode 100644 src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseAutocompleteItem.php create mode 100644 src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseCalculate.php create mode 100644 src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseLoadDeliveryData.php create mode 100644 src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseSave.php create mode 100644 src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseShipmentSave.php create mode 100644 src/Model/Callback/Entity/Delivery/SaveDeliveryData.php create mode 100644 src/Model/Callback/Entity/Delivery/SerializedStoreWeekOpeningHours.php create mode 100644 src/Model/Callback/Entity/Delivery/ShipmentOrder.php create mode 100644 src/Model/Callback/Entity/Delivery/Store.php create mode 100644 src/Model/Callback/Entity/Delivery/StoreWorkTime.php create mode 100644 src/Model/Callback/Entity/Delivery/Tariff.php create mode 100644 src/Model/Callback/Entity/Delivery/Terminal.php create mode 100644 src/Model/Callback/Entity/Delivery/Unit.php create mode 100644 src/Model/Callback/Entity/Integration/IntegrationModule.php create mode 100644 src/Model/Callback/Entity/Payments/ModuleApiRequest.php create mode 100644 src/Model/Callback/Entity/Payments/ModuleRefund.php create mode 100644 src/Model/Callback/Entity/Payments/PaymentCreateResult.php create mode 100644 src/Model/Callback/Entity/Store/InventoriesDataModel.php create mode 100644 src/Model/Callback/Entity/Store/OrderDataModel.php create mode 100644 src/Model/Callback/Entity/Store/PackDataModel.php create mode 100644 src/Model/Callback/Entity/Store/PackItemModel.php create mode 100644 src/Model/Callback/Enum/Recommendations/Mode.php create mode 100644 src/Model/Callback/Response/Delivery/AutocompleteResponse.php create mode 100644 src/Model/Callback/Response/Delivery/CalculateResponse.php create mode 100644 src/Model/Callback/Response/Delivery/GetResponse.php create mode 100644 src/Model/Callback/Response/Delivery/SaveResponse.php create mode 100644 src/Model/Callback/Response/Delivery/ShipmentPointListResponse.php create mode 100644 src/Model/Callback/Response/Delivery/ShipmentSaveResponse.php create mode 100644 src/Model/Callback/Response/Delivery/TariffListResponse.php create mode 100644 src/Model/Callback/Response/ErrorResponse.php create mode 100644 src/Model/Callback/Response/Payments/Item.php create mode 100644 src/Model/Callback/Response/Payments/PaymentCreateResponse.php create mode 100644 src/Model/Callback/Response/Payments/PaymentRefundResponse.php create mode 100644 src/Model/Callback/Response/Recommendation/RecommendationResponse.php create mode 100644 src/Model/Callback/Response/Store/InventoriesUploadResponse.php create mode 100644 src/Model/Callback/Response/Store/ReserveResponse.php create mode 100644 src/Model/Entity/CodeValueModel.php create mode 100644 src/Model/Entity/Costs/Cost.php create mode 100644 src/Model/Entity/Costs/CostOrder.php create mode 100644 src/Model/Entity/CustomFields/CustomDictionary.php create mode 100644 src/Model/Entity/CustomFields/CustomField.php create mode 100644 src/Model/Entity/CustomFields/SerializedCustomDictionaryElement.php create mode 100644 src/Model/Entity/Customers/Customer.php create mode 100644 src/Model/Entity/Customers/CustomerAddress.php create mode 100644 src/Model/Entity/Customers/CustomerContragent.php create mode 100644 src/Model/Entity/Customers/CustomerHistory.php create mode 100644 src/Model/Entity/Customers/CustomerNote.php create mode 100644 src/Model/Entity/Customers/CustomerPhone.php create mode 100644 src/Model/Entity/Customers/CustomerTag.php create mode 100644 src/Model/Entity/Customers/HistoryAddress.php create mode 100644 src/Model/Entity/Customers/Segment.php create mode 100644 src/Model/Entity/Customers/SerializedCustomerReference.php create mode 100644 src/Model/Entity/CustomersCorporate/Company.php create mode 100644 src/Model/Entity/CustomersCorporate/CustomerContact.php create mode 100644 src/Model/Entity/CustomersCorporate/CustomerContactCompany.php create mode 100644 src/Model/Entity/CustomersCorporate/CustomerCorporate.php create mode 100644 src/Model/Entity/CustomersCorporate/CustomerCorporateHistory.php create mode 100644 src/Model/Entity/CustomersCorporate/EntityWithExternalIdInput.php create mode 100644 src/Model/Entity/CustomersCorporate/EntityWithExternalIdNameOutput.php create mode 100644 src/Model/Entity/CustomersCorporate/SerializedEntityCustomer.php create mode 100644 src/Model/Entity/CustomersCorporate/SerializedRelationAbstractCustomer.php create mode 100644 src/Model/Entity/Delivery/DeliveryCalculation.php create mode 100644 src/Model/Entity/Delivery/DeliveryShipment.php create mode 100644 src/Model/Entity/Delivery/RequestStatusUpdateItem.php create mode 100644 src/Model/Entity/Delivery/SerializedEntityOrder.php create mode 100644 src/Model/Entity/Delivery/SerializedOrder.php create mode 100644 src/Model/Entity/Delivery/SerializedOrderDelivery.php create mode 100644 src/Model/Entity/Delivery/SerializedOrderProduct.php create mode 100644 src/Model/Entity/Delivery/StatusInfo.php create mode 100644 src/Model/Entity/Delivery/TimeInterval.php create mode 100644 src/Model/Entity/Files/Attachment.php create mode 100644 src/Model/Entity/Files/AttachmentCustomer.php create mode 100644 src/Model/Entity/Files/AttachmentOrder.php create mode 100644 src/Model/Entity/Files/File.php create mode 100644 src/Model/Entity/FixExternalRow.php create mode 100644 src/Model/Entity/HistoryApiKey.php create mode 100644 src/Model/Entity/HistoryUser.php create mode 100644 src/Model/Entity/IdModel.php create mode 100644 src/Model/Entity/Integration/Bot/BotConfiguration.php create mode 100644 src/Model/Entity/Integration/Delivery/DeliveryConfiguration.php create mode 100644 src/Model/Entity/Integration/Delivery/DeliveryDataField.php create mode 100644 src/Model/Entity/Integration/Delivery/PaymentType.php create mode 100644 src/Model/Entity/Integration/Delivery/Plate.php create mode 100644 src/Model/Entity/Integration/Delivery/Settings.php create mode 100644 src/Model/Entity/Integration/Delivery/ShipmentPoint.php create mode 100644 src/Model/Entity/Integration/Delivery/Status.php create mode 100644 src/Model/Entity/Integration/IntegrationModule.php create mode 100644 src/Model/Entity/Integration/IntegrationModuleEditInfo.php create mode 100644 src/Model/Entity/Integration/Integrations.php create mode 100644 src/Model/Entity/Integration/Payment/Actions.php create mode 100644 src/Model/Entity/Integration/Payment/PaymentConfiguration.php create mode 100644 src/Model/Entity/Integration/Payment/Shop.php create mode 100644 src/Model/Entity/Integration/Recommendation/Mode.php create mode 100644 src/Model/Entity/Integration/Recommendation/RecommendationConfiguration.php create mode 100644 src/Model/Entity/Integration/Store/Action.php create mode 100644 src/Model/Entity/Integration/Store/StoreConfiguration.php create mode 100644 src/Model/Entity/Integration/Telephony/SerializedAdditionalCodes.php create mode 100644 src/Model/Entity/Integration/Telephony/SerializedExternalPhones.php create mode 100644 src/Model/Entity/Integration/Telephony/TelephonyConfiguration.php create mode 100644 src/Model/Entity/Integration/Transport/TransportConfiguration.php create mode 100644 src/Model/Entity/Loyalty/Customer.php create mode 100644 src/Model/Entity/Loyalty/Loyalty.php create mode 100644 src/Model/Entity/Loyalty/LoyaltyAccount.php create mode 100644 src/Model/Entity/Loyalty/LoyaltyBonus.php create mode 100644 src/Model/Entity/Loyalty/LoyaltyCalculation.php create mode 100644 src/Model/Entity/Loyalty/LoyaltyEventDiscount.php create mode 100644 src/Model/Entity/Loyalty/LoyaltyLevel.php create mode 100644 src/Model/Entity/Loyalty/Operation.php create mode 100644 src/Model/Entity/Loyalty/OperationBonus.php create mode 100644 src/Model/Entity/Loyalty/OperationEvent.php create mode 100644 src/Model/Entity/Loyalty/OperationOrder.php create mode 100644 src/Model/Entity/Loyalty/OrderProduct.php create mode 100644 src/Model/Entity/Loyalty/OrderProductPriceItem.php create mode 100644 src/Model/Entity/Loyalty/PriceType.php create mode 100644 src/Model/Entity/Loyalty/SerializedCreateLoyaltyAccount.php create mode 100644 src/Model/Entity/Loyalty/SerializedLoyalty.php create mode 100644 src/Model/Entity/Loyalty/SerializedLoyaltyOrder.php create mode 100644 src/Model/Entity/Loyalty/SerializedOrder.php create mode 100644 src/Model/Entity/Loyalty/SerializedOrderDelivery.php create mode 100644 src/Model/Entity/Loyalty/SerializedOrderProduct.php create mode 100644 src/Model/Entity/Loyalty/SerializedOrderProductOffer.php create mode 100644 src/Model/Entity/Loyalty/SmsVerification.php create mode 100644 src/Model/Entity/Orders/Delivery/CourierPhone.php create mode 100644 src/Model/Entity/Orders/Delivery/DeliveryData.php create mode 100644 src/Model/Entity/Orders/Delivery/OrderDeliveryAddress.php create mode 100644 src/Model/Entity/Orders/Delivery/Package.php create mode 100644 src/Model/Entity/Orders/Delivery/PackageItem.php create mode 100644 src/Model/Entity/Orders/Delivery/PackageItemOrderProduct.php create mode 100644 src/Model/Entity/Orders/Delivery/SerializedDeliveryService.php create mode 100644 src/Model/Entity/Orders/Delivery/SerializedOrderDelivery.php create mode 100644 src/Model/Entity/Orders/Items/AbstractDiscount.php create mode 100644 src/Model/Entity/Orders/Items/ItemProperty.php create mode 100644 src/Model/Entity/Orders/Items/Offer.php create mode 100644 src/Model/Entity/Orders/Items/OrderProduct.php create mode 100644 src/Model/Entity/Orders/Items/OrderProductPriceItem.php create mode 100644 src/Model/Entity/Orders/Items/PriceType.php create mode 100644 src/Model/Entity/Orders/Items/Unit.php create mode 100644 src/Model/Entity/Orders/MarketplaceData.php create mode 100644 src/Model/Entity/Orders/Order.php create mode 100644 src/Model/Entity/Orders/OrderContragent.php create mode 100644 src/Model/Entity/Orders/OrderHistory.php create mode 100644 src/Model/Entity/Orders/OrderStatusItem.php create mode 100644 src/Model/Entity/Orders/Payment.php create mode 100644 src/Model/Entity/Orders/SerializedOrderLink.php create mode 100644 src/Model/Entity/Orders/SerializedOrderReference.php create mode 100644 src/Model/Entity/Orders/SerializedPayment.php create mode 100644 src/Model/Entity/Orders/SerializedRelationCustomer.php create mode 100644 src/Model/Entity/Packs/OrderProductPack.php create mode 100644 src/Model/Entity/Packs/OrderProductPackHistory.php create mode 100644 src/Model/Entity/Pagination.php create mode 100644 src/Model/Entity/Payments/ApiCheckRequest.php create mode 100644 src/Model/Entity/Payments/ApiCheckResponseResult.php create mode 100644 src/Model/Entity/Payments/ApiCreateInvoiceRequest.php create mode 100644 src/Model/Entity/Payments/ApiCreateInvoiceResponseResult.php create mode 100644 src/Model/Entity/Payments/ApiUpdateInvoiceRequest.php create mode 100644 src/Model/Entity/Payments/ModuleRefund.php create mode 100644 src/Model/Entity/References/CostGroup.php create mode 100644 src/Model/Entity/References/CostItem.php create mode 100644 src/Model/Entity/References/Courier.php create mode 100644 src/Model/Entity/References/DeliveryService.php create mode 100644 src/Model/Entity/References/DeliveryType.php create mode 100644 src/Model/Entity/References/GeoHierarchyRow.php create mode 100644 src/Model/Entity/References/LegalEntity.php create mode 100644 src/Model/Entity/References/MGChannel.php create mode 100644 src/Model/Entity/References/OrderMethod.php create mode 100644 src/Model/Entity/References/OrderProductStatus.php create mode 100644 src/Model/Entity/References/OrderType.php create mode 100644 src/Model/Entity/References/PaymentStatus.php create mode 100644 src/Model/Entity/References/PaymentType.php create mode 100644 src/Model/Entity/References/Point.php create mode 100644 src/Model/Entity/References/PriceType.php create mode 100644 src/Model/Entity/References/SerializedUnit.php create mode 100644 src/Model/Entity/References/Site.php create mode 100644 src/Model/Entity/References/Status.php create mode 100644 src/Model/Entity/References/StatusGroup.php create mode 100644 src/Model/Entity/References/Store.php create mode 100644 src/Model/Entity/References/StoreAddress.php create mode 100644 src/Model/Entity/References/StorePhone.php create mode 100644 src/Model/Entity/Settings/Settings.php create mode 100644 src/Model/Entity/Settings/Value.php create mode 100644 src/Model/Entity/Source.php create mode 100644 src/Model/Entity/Store/Inventory.php create mode 100644 src/Model/Entity/Store/Offer.php create mode 100644 src/Model/Entity/Store/OfferPrice.php create mode 100644 src/Model/Entity/Store/PriceUploadInput.php create mode 100644 src/Model/Entity/Store/PriceUploadNotFoundResponse.php create mode 100644 src/Model/Entity/Store/PriceUploadPricesInput.php create mode 100644 src/Model/Entity/Store/Product.php create mode 100644 src/Model/Entity/Store/ProductGroup.php create mode 100644 src/Model/Entity/Store/ProductOffer.php create mode 100644 src/Model/Entity/Tasks/AbstractCustomer.php create mode 100644 src/Model/Entity/Tasks/Order.php create mode 100644 src/Model/Entity/Tasks/Task.php create mode 100644 src/Model/Entity/Telephony/Call.php create mode 100644 src/Model/Entity/Telephony/CallEvent.php create mode 100644 src/Model/Entity/Telephony/SerializedCampaign.php create mode 100644 src/Model/Entity/Telephony/SerializedCustomer.php create mode 100644 src/Model/Entity/Telephony/SerializedLinks.php create mode 100644 src/Model/Entity/Telephony/SerializedManager.php create mode 100644 src/Model/Entity/Telephony/SerializedWebAnalyticsData.php create mode 100644 src/Model/Entity/Users/Group.php create mode 100644 src/Model/Entity/Users/User.php create mode 100644 src/Model/Entity/Verification/SmsVerification.php create mode 100644 src/Model/Entity/Verification/SmsVerificationConfirm.php create mode 100644 src/Model/Filter/Costs/CostFilter.php create mode 100644 src/Model/Filter/CustomFields/CustomDictionaryFilter.php create mode 100644 src/Model/Filter/CustomFields/CustomFieldFilter.php create mode 100644 src/Model/Filter/Customers/CustomerFilter.php create mode 100644 src/Model/Filter/Customers/CustomerHistoryFilter.php create mode 100644 src/Model/Filter/Customers/CustomerNoteFilter.php create mode 100644 src/Model/Filter/CustomersCorporate/CompanyFilter.php create mode 100644 src/Model/Filter/CustomersCorporate/CustomerAddressFilter.php create mode 100644 src/Model/Filter/CustomersCorporate/CustomerContactFilter.php create mode 100644 src/Model/Filter/CustomersCorporate/CustomerCorporateFilter.php create mode 100644 src/Model/Filter/Delivery/ApiDeliveryShipmentFilterType.php create mode 100644 src/Model/Filter/Files/FileFilter.php create mode 100644 src/Model/Filter/Loyalty/LoyaltyAccountApiFilterType.php create mode 100644 src/Model/Filter/Loyalty/LoyaltyAccountBonusOperationsApiFilterType.php create mode 100644 src/Model/Filter/Loyalty/LoyaltyApiFilterType.php create mode 100644 src/Model/Filter/Orders/OrderFilter.php create mode 100644 src/Model/Filter/Orders/OrderHistoryFilterV4Type.php create mode 100644 src/Model/Filter/Packs/OrderProductPackFilter.php create mode 100644 src/Model/Filter/Packs/OrderProductPackHistoryFilterType.php create mode 100644 src/Model/Filter/Segments/SegmentApiFilter.php create mode 100644 src/Model/Filter/Store/InventoryFilterType.php create mode 100644 src/Model/Filter/Store/ProductFilterType.php create mode 100644 src/Model/Filter/Store/ProductGroupFilterType.php create mode 100644 src/Model/Filter/Store/ProductPropertiesFilterType.php create mode 100644 src/Model/Filter/Store/ProductProperty.php create mode 100644 src/Model/Filter/Store/ProductPropertyGroup.php create mode 100644 src/Model/Filter/Tasks/TaskFilter.php create mode 100644 src/Model/Filter/Users/ApiUserFilter.php create mode 100644 src/Model/Request/BySiteRequest.php create mode 100644 src/Model/Request/Costs/CostsCreateRequest.php create mode 100644 src/Model/Request/Costs/CostsDeleteRequest.php create mode 100644 src/Model/Request/Costs/CostsEditRequest.php create mode 100644 src/Model/Request/Costs/CostsRequest.php create mode 100644 src/Model/Request/Costs/CostsUploadRequest.php create mode 100644 src/Model/Request/CustomFields/CustomDictionaryCreateRequest.php create mode 100644 src/Model/Request/CustomFields/CustomFieldsCreateRequest.php create mode 100644 src/Model/Request/CustomFields/CustomFieldsDictionariesRequest.php create mode 100644 src/Model/Request/CustomFields/CustomFieldsRequest.php create mode 100644 src/Model/Request/Customers/CustomersCombineRequest.php create mode 100644 src/Model/Request/Customers/CustomersCreateRequest.php create mode 100644 src/Model/Request/Customers/CustomersEditRequest.php create mode 100644 src/Model/Request/Customers/CustomersFixExternalIdsRequest.php create mode 100644 src/Model/Request/Customers/CustomersHistoryRequest.php create mode 100644 src/Model/Request/Customers/CustomersNotesCreateRequest.php create mode 100644 src/Model/Request/Customers/CustomersNotesRequest.php create mode 100644 src/Model/Request/Customers/CustomersRequest.php create mode 100644 src/Model/Request/Customers/CustomersUploadRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateAddressesCreateRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateAddressesEditRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateAddressesRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateCompaniesCreateRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateCompaniesEditRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateCompaniesRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateContactsCreateRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateContactsEditRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateContactsRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateCreateRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateEditRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateFixExternalIdsRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateRequest.php create mode 100644 src/Model/Request/CustomersCorporate/CustomersCorporateUploadRequest.php create mode 100644 src/Model/Request/Delivery/DeliveryCalculateRequest.php create mode 100644 src/Model/Request/Delivery/DeliveryShipmentsCreateRequest.php create mode 100644 src/Model/Request/Delivery/DeliveryShipmentsRequest.php create mode 100644 src/Model/Request/Delivery/TrackingRequest.php create mode 100644 src/Model/Request/Files/FilesEditRequest.php create mode 100644 src/Model/Request/Files/FilesRequest.php create mode 100644 src/Model/Request/Files/FilesUploadRequest.php create mode 100644 src/Model/Request/Integration/IntegrationModulesEditRequest.php create mode 100644 src/Model/Request/Loyalty/LoyaltiesRequest.php create mode 100644 src/Model/Request/Loyalty/LoyaltyAccountCreateRequest.php create mode 100644 src/Model/Request/Loyalty/LoyaltyAccountEditRequest.php create mode 100644 src/Model/Request/Loyalty/LoyaltyAccountsRequest.php create mode 100644 src/Model/Request/Loyalty/LoyaltyBonusCreditRequest.php create mode 100644 src/Model/Request/Loyalty/LoyaltyBonusOperationsRequest.php create mode 100644 src/Model/Request/Loyalty/LoyaltyCalculateRequest.php create mode 100644 src/Model/Request/Orders/OrdersCombineRequest.php create mode 100644 src/Model/Request/Orders/OrdersCreateRequest.php create mode 100644 src/Model/Request/Orders/OrdersEditRequest.php create mode 100644 src/Model/Request/Orders/OrdersFixExternalIdsRequest.php create mode 100644 src/Model/Request/Orders/OrdersHistoryRequest.php create mode 100644 src/Model/Request/Orders/OrdersLinksCreateRequest.php create mode 100644 src/Model/Request/Orders/OrdersLoyaltyApplyRequest.php create mode 100644 src/Model/Request/Orders/OrdersPaymentsCreateRequest.php create mode 100644 src/Model/Request/Orders/OrdersPaymentsEditRequest.php create mode 100644 src/Model/Request/Orders/OrdersRequest.php create mode 100644 src/Model/Request/Orders/OrdersStatusesRequest.php create mode 100644 src/Model/Request/Orders/OrdersUploadRequest.php create mode 100644 src/Model/Request/Packs/PacksCreateRequest.php create mode 100644 src/Model/Request/Packs/PacksHistoryRequest.php create mode 100644 src/Model/Request/Packs/PacksRequest.php create mode 100644 src/Model/Request/Payments/PaymentCheckRequest.php create mode 100644 src/Model/Request/Payments/PaymentCreateInvoiceRequest.php create mode 100644 src/Model/Request/Payments/PaymentUpdateInvoiceRequest.php create mode 100644 src/Model/Request/References/CostGroupsEditRequest.php create mode 100644 src/Model/Request/References/CostItemsEditRequest.php create mode 100644 src/Model/Request/References/CouriersCreateRequest.php create mode 100644 src/Model/Request/References/DeliveryServicesEditRequest.php create mode 100644 src/Model/Request/References/DeliveryTypesEditRequest.php create mode 100644 src/Model/Request/References/LegalEntityEditRequest.php create mode 100644 src/Model/Request/References/OrderMethodsEditRequest.php create mode 100644 src/Model/Request/References/OrderTypesEditRequest.php create mode 100644 src/Model/Request/References/PaymentStatusesEditRequest.php create mode 100644 src/Model/Request/References/PaymentTypesEditRequest.php create mode 100644 src/Model/Request/References/PriceTypesEditRequest.php create mode 100644 src/Model/Request/References/ProductStatusesEditRequest.php create mode 100644 src/Model/Request/References/SitesEditRequest.php create mode 100644 src/Model/Request/References/StatusesEditRequest.php create mode 100644 src/Model/Request/References/StoresEditRequest.php create mode 100644 src/Model/Request/References/UnitsEditRequest.php create mode 100644 src/Model/Request/Segments/SegmentsRequest.php create mode 100644 src/Model/Request/Store/InventoriesRequest.php create mode 100644 src/Model/Request/Store/InventoriesUploadRequest.php create mode 100644 src/Model/Request/Store/PricesUploadRequest.php create mode 100644 src/Model/Request/Store/ProductGroupsRequest.php create mode 100644 src/Model/Request/Store/ProductPropertiesRequest.php create mode 100644 src/Model/Request/Store/ProductsRequest.php create mode 100644 src/Model/Request/Tasks/TasksCreateRequest.php create mode 100644 src/Model/Request/Tasks/TasksRequest.php create mode 100644 src/Model/Request/Telephony/TelephonyCallEventRequest.php create mode 100644 src/Model/Request/Telephony/TelephonyCallsUploadRequest.php create mode 100644 src/Model/Request/Telephony/TelephonyManagerRequest.php create mode 100644 src/Model/Request/Traits/HistoryPageLimitTrait.php create mode 100644 src/Model/Request/Traits/PageLimitTrait.php create mode 100644 src/Model/Request/Users/UserGroupsRequest.php create mode 100644 src/Model/Request/Users/UsersRequest.php create mode 100644 src/Model/Request/Users/UsersStatusRequest.php create mode 100644 src/Model/Request/Verification/SmsVerificationConfirmRequest.php create mode 100644 src/Model/RequestData.php create mode 100644 src/Model/Response/AbstractPaginatedResponse.php create mode 100644 src/Model/Response/Api/ApiVersionsResponse.php create mode 100644 src/Model/Response/Api/Credentials.php create mode 100644 src/Model/Response/Costs/CostsDeleteResponse.php create mode 100644 src/Model/Response/Costs/CostsGetResponse.php create mode 100644 src/Model/Response/Costs/CostsResponse.php create mode 100644 src/Model/Response/Costs/CostsUploadResponse.php create mode 100644 src/Model/Response/CustomFields/CustomDictionaryCreateResponse.php create mode 100644 src/Model/Response/CustomFields/CustomDictionaryGetResponse.php create mode 100644 src/Model/Response/CustomFields/CustomFieldsCreateResponse.php create mode 100644 src/Model/Response/CustomFields/CustomFieldsDictionariesResponse.php create mode 100644 src/Model/Response/CustomFields/CustomFieldsEditResponse.php create mode 100644 src/Model/Response/CustomFields/CustomFieldsGetResponse.php create mode 100644 src/Model/Response/CustomFields/CustomFieldsResponse.php create mode 100644 src/Model/Response/Customers/CustomerNotesResponse.php create mode 100644 src/Model/Response/Customers/CustomersEditResponse.php create mode 100644 src/Model/Response/Customers/CustomersGetResponse.php create mode 100644 src/Model/Response/Customers/CustomersHistoryResponse.php create mode 100644 src/Model/Response/Customers/CustomersResponse.php create mode 100644 src/Model/Response/Customers/CustomersUploadResponse.php create mode 100644 src/Model/Response/CustomersCorporate/CustomersCorporateAddressesResponse.php create mode 100644 src/Model/Response/CustomersCorporate/CustomersCorporateCompaniesResponse.php create mode 100644 src/Model/Response/CustomersCorporate/CustomersCorporateContactsResponse.php create mode 100644 src/Model/Response/CustomersCorporate/CustomersCorporateGetResponse.php create mode 100644 src/Model/Response/CustomersCorporate/CustomersCorporateHistoryResponse.php create mode 100644 src/Model/Response/CustomersCorporate/CustomersCorporateResponse.php create mode 100644 src/Model/Response/Delivery/DeliveryCalculateResponse.php create mode 100644 src/Model/Response/Delivery/DeliveryShipmentsCreateResponse.php create mode 100644 src/Model/Response/Delivery/DeliveryShipmentsGetResponse.php create mode 100644 src/Model/Response/Delivery/DeliveryShipmentsResponse.php create mode 100644 src/Model/Response/ErrorResponse.php create mode 100644 src/Model/Response/Files/FilesDownloadResponse.php create mode 100644 src/Model/Response/Files/FilesGetResponse.php create mode 100644 src/Model/Response/Files/FilesResponse.php create mode 100644 src/Model/Response/Files/FilesUploadResponse.php create mode 100644 src/Model/Response/IdResponse.php create mode 100644 src/Model/Response/Integration/IntegrationModulesEditResponse.php create mode 100644 src/Model/Response/Integration/IntegrationModulesGetResponse.php create mode 100644 src/Model/Response/Loyalty/LoyaltiesResponse.php create mode 100644 src/Model/Response/Loyalty/LoyaltyAccountActivateResponse.php create mode 100644 src/Model/Response/Loyalty/LoyaltyAccountCreateResponse.php create mode 100644 src/Model/Response/Loyalty/LoyaltyAccountsResponse.php create mode 100644 src/Model/Response/Loyalty/LoyaltyBonusCreditResponse.php create mode 100644 src/Model/Response/Loyalty/LoyaltyBonusOperationsResponse.php create mode 100644 src/Model/Response/Loyalty/LoyaltyCalculateResponse.php create mode 100644 src/Model/Response/Orders/OrdersCombineResponse.php create mode 100644 src/Model/Response/Orders/OrdersCreateResponse.php create mode 100644 src/Model/Response/Orders/OrdersGetResponse.php create mode 100644 src/Model/Response/Orders/OrdersHistoryResponse.php create mode 100644 src/Model/Response/Orders/OrdersLoyaltyApplyResponse.php create mode 100644 src/Model/Response/Orders/OrdersResponse.php create mode 100644 src/Model/Response/Orders/OrdersStatusesResponse.php create mode 100644 src/Model/Response/Orders/OrdersUploadResponse.php create mode 100644 src/Model/Response/Packs/PacksGetResponse.php create mode 100644 src/Model/Response/Packs/PacksHistoryResponse.php create mode 100644 src/Model/Response/Packs/PacksResponse.php create mode 100644 src/Model/Response/Payments/PaymentCheckResponse.php create mode 100644 src/Model/Response/Payments/PaymentCreateInvoiceResponse.php create mode 100644 src/Model/Response/References/CostGroupsResponse.php create mode 100644 src/Model/Response/References/CostItemsResponse.php create mode 100644 src/Model/Response/References/CountriesResponse.php create mode 100644 src/Model/Response/References/CouriersResponse.php create mode 100644 src/Model/Response/References/DeliveryServicesResponse.php create mode 100644 src/Model/Response/References/DeliveryTypesResponse.php create mode 100644 src/Model/Response/References/LegalEntitiesResponse.php create mode 100644 src/Model/Response/References/MgChannelsResponse.php create mode 100644 src/Model/Response/References/OrderMethodsResponse.php create mode 100644 src/Model/Response/References/OrderTypesResponse.php create mode 100644 src/Model/Response/References/PaymentStatusesResponse.php create mode 100644 src/Model/Response/References/PaymentTypesResponse.php create mode 100644 src/Model/Response/References/PriceTypesResponse.php create mode 100644 src/Model/Response/References/ProductStatusesResponse.php create mode 100644 src/Model/Response/References/SitesResponse.php create mode 100644 src/Model/Response/References/StatusGroupsResponse.php create mode 100644 src/Model/Response/References/StatusesResponse.php create mode 100644 src/Model/Response/References/StoresResponse.php create mode 100644 src/Model/Response/References/UnitsResponse.php create mode 100644 src/Model/Response/Segments/SegmentsResponse.php create mode 100644 src/Model/Response/Settings/SettingsResponse.php create mode 100644 src/Model/Response/Store/InventoriesResponse.php create mode 100644 src/Model/Response/Store/InventoriesUploadResponse.php create mode 100644 src/Model/Response/Store/PricesUploadResponse.php create mode 100644 src/Model/Response/Store/ProductGroupsResponse.php create mode 100644 src/Model/Response/Store/ProductPropertiesResponse.php create mode 100644 src/Model/Response/Store/ProductsResponse.php create mode 100644 src/Model/Response/SuccessResponse.php create mode 100644 src/Model/Response/Tasks/TasksGetResponse.php create mode 100644 src/Model/Response/Tasks/TasksResponse.php create mode 100644 src/Model/Response/Telephony/CallEventResponse.php create mode 100644 src/Model/Response/Telephony/CallsUploadResponse.php create mode 100644 src/Model/Response/Telephony/ManagerResponse.php create mode 100644 src/Model/Response/Users/UserGroupsResponse.php create mode 100644 src/Model/Response/Users/UsersGetResponse.php create mode 100644 src/Model/Response/Users/UsersResponse.php create mode 100644 src/Model/Response/Verification/SmsVerificationConfirmResponse.php create mode 100644 src/Model/Response/Verification/SmsVerificationStatusResponse.php create mode 100644 src/Model/ResponseData.php create mode 100644 src/ResourceGroup/AbstractApiResourceGroup.php create mode 100644 src/ResourceGroup/Api.php create mode 100644 src/ResourceGroup/Costs.php create mode 100644 src/ResourceGroup/CustomFields.php create mode 100644 src/ResourceGroup/Customers.php create mode 100644 src/ResourceGroup/CustomersCorporate.php create mode 100644 src/ResourceGroup/Delivery.php create mode 100644 src/ResourceGroup/Files.php create mode 100644 src/ResourceGroup/Integration.php create mode 100644 src/ResourceGroup/Loyalty.php create mode 100644 src/ResourceGroup/Orders.php create mode 100644 src/ResourceGroup/Packs.php create mode 100644 src/ResourceGroup/Payments.php create mode 100644 src/ResourceGroup/References.php create mode 100644 src/ResourceGroup/Segments.php create mode 100644 src/ResourceGroup/Settings.php create mode 100644 src/ResourceGroup/Statistics.php create mode 100644 src/ResourceGroup/Store.php create mode 100644 src/ResourceGroup/Tasks.php create mode 100644 src/ResourceGroup/Telephony.php create mode 100644 src/ResourceGroup/Users.php create mode 100644 src/ResourceGroup/Verification.php create mode 100644 src/Traits/ApiExceptionFactoryAwareTrait.php create mode 100644 src/Traits/EventDispatcherAwareTrait.php create mode 100644 src/Traits/PsrFactoriesAwareTrait.php create mode 100644 src/Traits/SerializerAwareTrait.php delete mode 100644 tests/RetailCrm/Test/TestCase.php delete mode 100644 tests/RetailCrm/Tests/ApiClientTest.php delete mode 100644 tests/RetailCrm/Tests/Http/ClientTest.php delete mode 100644 tests/RetailCrm/Tests/Http/RequestOptionsTest.php delete mode 100755 tests/RetailCrm/Tests/Methods/CommonMethodsTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version4/ApiClientCustomersTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version4/ApiClientMarketplaceTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version4/ApiClientOrdersTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version4/ApiClientPacksTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version4/ApiClientPricesTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version4/ApiClientReferenceTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version4/ApiClientStoreTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version4/ApiClientTelephonyTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version4/ApiClientUsersTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version5/ApiClientCustomersCorporateTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version5/ApiClientCustomersTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version5/ApiClientDeliveryTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version5/ApiClientFilesTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version5/ApiClientMarketplaceTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version5/ApiClientOrdersTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version5/ApiClientPacksTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version5/ApiClientPricesTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version5/ApiClientReferenceTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version5/ApiClientStoreTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version5/ApiClientTasksTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version5/ApiClientTelephonyTest.php delete mode 100644 tests/RetailCrm/Tests/Methods/Version5/ApiClientUsersTest.php delete mode 100644 tests/RetailCrm/Tests/Resources/Report.pdf delete mode 100644 tests/RetailCrm/Tests/Response/ApiResponseTest.php create mode 100644 tests/src/Builder/ClientBuilderTest.php create mode 100644 tests/src/ClientTest.php create mode 100644 tests/src/Command/ClearModelsCommandTest.php create mode 100644 tests/src/Command/CompilerPromptCommandTest.php create mode 100644 tests/src/Command/GenerateModelsCommandTest.php create mode 100644 tests/src/Command/VerifyModelsCommandTest.php create mode 100644 tests/src/Component/ComposerLocatorTest.php create mode 100644 tests/src/Component/FilesIteratorChecksumGeneratorTest.php create mode 100644 tests/src/Component/PhpFilesIteratorTest.php create mode 100644 tests/src/Component/Serializer/ModelsChecksumGeneratorTest.php create mode 100644 tests/src/Exception/ClientExceptionTest.php create mode 100644 tests/src/Factory/ApiExceptionFactoryTest.php create mode 100644 tests/src/Factory/ClientFactoryTest.php create mode 100644 tests/src/Factory/SimpleClientFactoryTest.php create mode 100644 tests/src/Handler/Request/CallbackRequestHandlerTest.php create mode 100644 tests/src/Handler/Response/CallbackResponseHandlerTest.php create mode 100644 tests/src/Model/Callback/Entity/Delivery/CustomerTest.php create mode 100644 tests/src/ResourceGroup/AbstractApiResourceGroupTest.php create mode 100644 tests/src/ResourceGroup/ApiTest.php create mode 100644 tests/src/ResourceGroup/CostsTest.php create mode 100644 tests/src/ResourceGroup/CustomFieldsTest.php create mode 100644 tests/src/ResourceGroup/CustomersCorporateTest.php create mode 100644 tests/src/ResourceGroup/CustomersTest.php create mode 100644 tests/src/ResourceGroup/DeliveryTest.php create mode 100644 tests/src/ResourceGroup/ErrorTest.php create mode 100644 tests/src/ResourceGroup/FilesTest.php create mode 100644 tests/src/ResourceGroup/IntegrationTests.php create mode 100644 tests/src/ResourceGroup/LoyaltyTest.php create mode 100644 tests/src/ResourceGroup/OrdersTest.php create mode 100644 tests/src/ResourceGroup/PacksTest.php create mode 100644 tests/src/ResourceGroup/PaymentsTest.php create mode 100644 tests/src/ResourceGroup/ReferencesTest.php create mode 100644 tests/src/ResourceGroup/SegmentsTest.php create mode 100644 tests/src/ResourceGroup/SettingsTest.php create mode 100644 tests/src/ResourceGroup/StatisticsTest.php create mode 100644 tests/src/ResourceGroup/StoreTest.php create mode 100644 tests/src/ResourceGroup/TasksTest.php create mode 100644 tests/src/ResourceGroup/TelephonyTest.php create mode 100644 tests/src/ResourceGroup/UsersTest.php create mode 100644 tests/src/ResourceGroup/VerificationTest.php create mode 100644 tests/utils/ArrayLogger.php create mode 100644 tests/utils/ClientFactoryDependentService.php create mode 100644 tests/utils/Exception/MatcherException.php create mode 100644 tests/utils/Factory/TestClientFactory.php create mode 100644 tests/utils/ReflectionUtils.php create mode 100644 tests/utils/TestCase/AbstractApiResourceGroupTestCase.php create mode 100644 tests/utils/TestCase/ClientTestCase.php create mode 100644 tests/utils/TestConfig.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5363627 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{less,css,yml,json}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.env.dist b/.env.dist new file mode 100644 index 0000000..36920b9 --- /dev/null +++ b/.env.dist @@ -0,0 +1,6 @@ +# This file can be used change api url and api key in tests. Useful when you want to use real networking for test case. +# Follow these steps if you want to use real networking for your test case: +# - Replace credentials below with your own. +# - Use `php-http/curl-client` as an argument for `TestClientFactory::createClient`. +API_URL=https://test.retailcrm.pro/ +API_KEY=testkey diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53f73ce..3be8a2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: ci +name: CI on: push: @@ -8,26 +8,25 @@ on: - '*.*' pull_request: -env: - RETAILCRM_URL: ${{ secrets.RETAILCRM_URL }} - RETAILCRM_KEY: ${{ secrets.RETAILCRM_KEY }} - RETAILCRM_VERSION: ${{ secrets.RETAILCRM_VERSION }} - RETAILCRM_SITE: ${{ secrets.RETAILCRM_SITE }} - RETAILCRM_USER: ${{ secrets.RETAILCRM_USER }} - jobs: test: + name: "PHPUnit" runs-on: ubuntu-latest strategy: matrix: - php-version: ['7.0', '7.1', '7.2', '7.3', '7.4'] + php-version: ['7.3', '7.4'] +# TODO These dependencies blocks PHP 8.0 support for us: +# - `liip/serializer` and `liip/metadata-parser` - shouldn't be a big deal. It's clearly possible to fix that. +# - `phpmd/phpmd` - doesn't work for PHP 8.0 (it won't find any errors). Requires big changes in its dependencies, but work is already being done. +# php-version: ['7.3', '7.4', '8.0'] steps: - - uses: actions/checkout@v2 + - name: Check out code into the workspace + uses: actions/checkout@v2 - name: Setup PHP ${{ matrix.php-version }} uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} - coverage: xdebug + coverage: pcov - name: Composer cache uses: actions/cache@v2 with: @@ -35,7 +34,9 @@ jobs: key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - name: Install dependencies run: composer install -o + - name: Configure matchers + uses: mheap/phpunit-matcher-action@v1 - name: Run tests - run: php ./vendor/phpunit/phpunit/phpunit -c phpunit.xml.dist + run: composer run-script phpunit-ci - name: Coverage run: bash <(curl -s https://codecov.io/bash) diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml new file mode 100644 index 0000000..8fed489 --- /dev/null +++ b/.github/workflows/code_quality.yml @@ -0,0 +1,42 @@ +name: "Code Quality Check" + +on: + pull_request: + paths: + - "**.php" + - "phpcs.xml" + - ".github/workflows/code_quality.yml" + +jobs: + phpcs: + name: "PHP CodeSniffer" + runs-on: ubuntu-latest + steps: + - name: Check out code into the workspace + uses: actions/checkout@v2 + - name: Run PHPCS + uses: chekalsky/phpcs-action@v1 + phpmd: + name: "PHP MessDetector" + runs-on: ubuntu-latest + steps: + - name: Check out code into the workspace + uses: actions/checkout@v2 + - name: Run PHPMD + uses: GeneaLabs/action-reviewdog-phpmd@1.0.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + level: 'warning' + reporter: github-pr-check + standard: './phpmd.xml' + target_directory: 'src' + phpstan: + name: PHPStan + runs-on: ubuntu-18.04 + steps: + - name: Check out code into the workspace + uses: actions/checkout@v2 + - name: Run PHPStan + uses: docker://oskarstark/phpstan-ga:0.12.88 + with: + args: analyse src -c phpstan.neon --memory-limit=1G --no-progress diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..6e201a6 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,47 @@ +name: phpDocumentor + +on: + push: + branches: + - 'master' + tags: + - 'v*' + +jobs: + test: + name: "phpDocumentor" + runs-on: ubuntu-latest + steps: + - name: Check GitHub Pages status + if: ${{ github.ref != 'refs/heads/master' }} + uses: crazy-max/ghaction-github-status@v2 + with: + pages_threshold: major_outage + - name: Check out code into the workspace + if: success() && ${{ github.ref != 'refs/heads/master' }} + uses: actions/checkout@v2 + - name: Setup PHP 7.4 + if: ${{ github.ref != 'refs/heads/master' }} + uses: shivammathur/setup-php@v2 + with: + php-version: "7.4" + - name: Cache phpDocumentor + id: cache-phpdocumentor + uses: actions/cache@v2 + with: + path: phpDocumentor.phar + key: phpdocumentor + - name: Download latest phpDocumentor + if: steps.cache-phpdocumentor.outputs.cache-hit != 'true' + run: curl -O -L https://phpdoc.org/phpDocumentor.phar + - name: Generate documentation + if: ${{ github.ref != 'refs/heads/master' }} + run: php phpDocumentor.phar + - name: Deploy documentation to GitHub Pages + if: ${{ github.ref != 'refs/heads/master' }} + uses: crazy-max/ghaction-github-pages@v2 + with: + target_branch: gh-pages + build_dir: docs/build/html + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index c7050eb..37e1e60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,25 @@ +# Composer files. /vendor -/bin composer.lock composer.phar + +# Code Quality tools artifacts. coverage.xml test-report.xml phpunit.xml +.php_cs.cache +.phpunit.result.cache + +# phpDocumentor files. +.phpdoc +docs +phpDocumentor.phar + +# Ignore autogenerated code. +models/*.php +models/checksum.json + +# Different environment-related files. .idea .DS_Store .settings @@ -12,3 +27,4 @@ phpunit.xml .project .swp /nbproject +.env diff --git a/README.md b/README.md index 260e460..883084c 100644 --- a/README.md +++ b/README.md @@ -1,125 +1,293 @@ -[![Build Status](https://github.com/retailcrm/api-client-php/workflows/ci/badge.svg)](https://github.com/retailcrm/api-client-php/actions) -[![Covarage](https://img.shields.io/codecov/c/gh/retailcrm/api-client-php/master.svg?logo=codecov&logoColor=white)](https://codecov.io/gh/retailcrm/api-client-php) +[![Build Status](https://github.com/retailcrm/api-client-php/workflows/CI/badge.svg)](https://github.com/retailcrm/api-client-php/actions) +[![Coverage](https://img.shields.io/codecov/c/gh/retailcrm/api-client-php/master.svg?logo=codecov&logoColor=white)](https://codecov.io/gh/retailcrm/api-client-php) [![Latest stable](https://img.shields.io/packagist/v/retailcrm/api-client-php.svg)](https://packagist.org/packages/retailcrm/api-client-php) [![PHP from Packagist](https://img.shields.io/packagist/php-v/retailcrm/api-client-php.svg?logo=php&logoColor=white)](https://packagist.org/packages/retailcrm/api-client-php) # RetailCRM API PHP client -This is php RetailCRM API client. This library allows to use all available API versions. [API documentation](http://retailcrm.github.io/api-client-php) +This is the PHP RetailCRM API client. This library allows using of the actual API version. +You can find more info in the [documentation](doc/index.md). + +# Table of contents + +* [Requirements](#requirements) +* [Installation](#installation) +* [Usage](#usage) +* [Examples](#examples) +* [Notes](#notes) +* [Documentation](doc/index.md) ## Requirements -* PHP 5.4 and above +* PHP 7.3 and above * PHP's cURL support * PHP's JSON support -* PHP's Fileinfo support +* Any HTTP client compatible with PSR-18 (covered by the installation instructions). +* Any HTTP factories implementation compatible with PSR-17 (covered by the installation instructions). +* Any HTTP messages implementation compatible with PSR-7 (covered by the installation instructions). +* Other dependencies listed in the `composer.json` (covered by the installation instructions) -## Install +## Installation -1) Get [composer](https://getcomposer.org/download/) +Follow those steps to install the library: -2) Run into your project directory: +1. Download and install [Composer](https://getcomposer.org/download/) package manager. +2. Install the library from the Packagist by executing this command: ```bash -composer require retailcrm/api-client-php ~5.0 +composer require retailcrm/api-client-php:"~6.0" +``` +During the installation, you'll see a message which will look like this: +```sh +The following packages have new compilation tasks: + - retailcrm/api-client-php has 1 task + +Allow these packages to compile? ([y]es, [a]lways, [n]o, [l]ist, [h]elp) +``` +That's because the Client uses code generation to speed up serialization and deserialization of models in production. This code should be generated during installation or update. Without that code, the library itself will not work at all. + +Just type `y` here and press Enter. The DTO cache will be generated after that. + +If you skipped the compilation task - don't worry, it can be executed manually at any time with this command: +```sh +composer compile --all ``` -If you have not used `composer` before, include autoloader into your project. +3. **Optional.** Disable compilation prompt that you have seen in the previous step. Run this command to do that: + +```sh +./vendor/bin/retailcrm-client compiler:prompt +``` + +Replace `vendor/bin` with your bin directory path if it's different from the default. You can find more information about this step in the [documentation](doc/compilation_prompt.md). + +**Note:** You should not skip this step if your application is using CI/CD pipeline because the interactive terminal is not available +in that environment which will result in failure during the dependencies installation. + +4. Include the autoloader if it's not included, or you didn't use Composer before. ```php require 'path/to/vendor/autoload.php'; ``` +Replace `path/to/vendor/autoload.php` with the correct path to Composer's `autoload.php`. + +**Note:** API client uses `php-http/curl-client` and `nyholm/psr7` as a PSR-18, PSR-17 and PSR-7 implementation. +You can replace those implementations during installation by installing this library with the implementation of your choice, like this: +```sh +composer require symfony/http-client guzzlehttp/psr7 retailcrm/api-client-php:"~6.0" +``` + +More information about that can be found in the [documentation](doc/customization/different_psr_implementations.md). + ## Usage -### Get order +Firstly, you should initialize the Client. The easiest way to do this is to use the `SimpleClientFactory`: + ```php -$client = new \RetailCrm\ApiClient( - 'https://demo.retailcrm.pro', - 'T9DMPvuNt7FQJMszHUdG8Fkt6xHsqngH', - \RetailCrm\ApiClient::V5 -); +$client = \RetailCrm\Api\Factory\SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); +``` + +The client is separated into several resource groups, all of which are accessible through the Client's public properties. +You can call API methods from those groups like this: + +```php +$client->api->credentials(); +``` + +For example, you can retrieve the customers list: + +```php +$client = \RetailCrm\Api\Factory\SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); +$response = $client->customers->list(); +``` + +Or the orders list: + +```php +$client = \RetailCrm\Api\Factory\SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); +$response = $client->orders->list(); +``` + +To handle errors you must use two types of exceptions: +* `RetailCrm\Api\Interfaces\ClientExceptionInterface` for the network or other runtime errors. +* `RetailCrm\Api\Interfaces\ApiExceptionInterface` for the errors from the API. + +An example of error handling can be found in the next section of this document. + +Each resource group is responsible for the corresponding API section. For example, `costs` resource group provide methods +for costs manipulation and `loyalty` resource group allows interacting with loyalty programs, accounts, bonuses, etc. + +Use annotations to determine which DTOs you need for sending the requests. If annotations are not provided by your IDE - you +probably should configure them. It'll ease your work with this (and any other) library a lot. + +More information about the usage including examples can be found in the [documentation](doc/usage/usage.md). + +## Examples + +Listing orders: + +```php +request->ordersGet('M-2342'); -} catch (\RetailCrm\Exception\CurlException $e) { - echo "Connection error: " . $e->getMessage(); + $response = $client->orders->list(); +} catch (ApiExceptionInterface | ClientExceptionInterface $exception) { + echo $exception; // Every ApiExceptionInterface and ClientExceptionInterface instance implements __toString() method. + exit(-1); } -if ($response->isSuccessful()) { - echo $response->order['totalSumm']; - // or $response['order']['totalSumm']; - // or - // $order = $response->getOrder(); - // $order['totalSumm']; -} else { - echo sprintf( - "Error: [HTTP-code %s] %s", - $response->getStatusCode(), - $response->getErrorMsg() - ); +foreach ($response->orders as $order) { + printf("Order ID: %d\n", $order->id); + printf("First name: %s\n", $order->firstName); + printf("Last name: %s\n", $order->lastName); + printf("Patronymic: %s\n", $order->patronymic); + printf("Phone #1: %s\n", $order->phone); + printf("Phone #2: %s\n", $order->additionalPhone); + printf("E-Mail: %s\n", $order->email); - // error details - //if (isset($response['errors'])) { - // print_r($response['errors']); - //} + if ($order->customer instanceof CustomerCorporate) { + echo "Customer type: corporate\n"; + } else { + echo "Customer type: individual\n"; + } + + foreach ($order->items as $item) { + echo PHP_EOL; + + printf("Product name: %s\n", $item->productName); + printf("Quantity: %d\n", $item->quantity); + printf("Initial price: %f\n", $item->initialPrice); + } + + echo PHP_EOL; + + printf("Discount: %f\n", $order->discountManualAmount); + printf("Total: %f\n", $order->totalSumm); + + echo PHP_EOL; } ``` -### Create order -```php +Fetching a specific order by it's ID: -$client = new \RetailCrm\ApiClient( - 'https://demo.retailcrm.pro', - 'T9DMPvuNt7FQJMszHUdG8Fkt6xHsqngH', - \RetailCrm\ApiClient::V5 -); +```php +request->ordersCreate(array( - 'externalId' => 'some-shop-order-id', - 'firstName' => 'John', - 'lastName' => 'Doe', - 'items' => array( - //... - ), - 'delivery' => array( - 'code' => 'fedex', - ) - )); -} catch (\RetailCrm\Exception\CurlException $e) { - echo "Connection error: " . $e->getMessage(); + $response = $client->orders->get(1234, new BySiteRequest(ByIdentifier::ID, 'site')); +} catch (ApiExceptionInterface | ClientExceptionInterface $exception) { + echo $exception; // Every ApiExceptionInterface instance should implement __toString() method. + exit(-1); } -if ($response->isSuccessful() && 201 === $response->getStatusCode()) { - echo 'Order successfully created. Order ID into RetailCRM = ' . $response->id; - // or $response['id']; - // or $response->getId(); -} else { - echo sprintf( - "Error: [HTTP-code %s] %s", - $response->getStatusCode(), - $response->getErrorMsg() - ); - - // error details - //if (isset($response['errors'])) { - // print_r($response['errors']); - //} -} +echo 'Order: ' . print_r($response->order, true); ``` -### Set custom headers and client timeout +Creating a new customer: + ```php -$client = new \RetailCrm\ApiClient( - 'https://demo.retailcrm.pro', - 'T9DMPvuNt7FQJMszHUdG8Fkt6xHsqngH', - \RetailCrm\ApiClient::V5 -); + 'example_token'], // array of custom headers - 10 // client timeout (in seconds) -); +use RetailCrm\Api\Interfaces\ClientExceptionInterface; +use RetailCrm\Api\Interfaces\ApiExceptionInterface; +use RetailCrm\Api\Factory\SimpleClientFactory; +use RetailCrm\Api\Model\Entity\Customers\Customer; +use RetailCrm\Api\Model\Request\Customers\CustomersCreateRequest; -$client->request->setOptions($options); +$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + +$request = new CustomersCreateRequest(); +$request->customer = new Customer(); + +$request->site = 'aliexpress'; +$request->customer->email = 'john.doe@example.com'; +$request->customer->firstName = 'John'; +$request->customer->lastName = 'Doe'; + +try { + $response = $client->customers->create($request); +} catch (ApiExceptionInterface | ClientExceptionInterface $exception) { + echo $exception; // Every ApiExceptionInterface instance should implement __toString() method. + exit(-1); +} + +echo 'Customer ID: ' . $response->id; ``` + +Creating a task for the user with a specific email: + +```php +filter = new ApiUserFilter(); +$usersRequest->filter->email = 'john.doe@example.com'; + +try { + $usersResponse = $client->users->list($usersRequest); +} catch (ApiExceptionInterface | ClientExceptionInterface $exception) { + echo $exception; // Every ApiExceptionInterface instance should implement __toString() method. + exit(-1); +} + +if (0 === count($usersResponse->users)) { + echo 'User is not found.'; + exit(-1); +} + +$tasksRequest = new TasksCreateRequest(); +$tasksRequest->task = new Task(); +$tasksRequest->task->performerId = $usersResponse->users[0]->id; +$tasksRequest->task->text = 'Do something!'; +$tasksRequest->site = 'site'; + +try { + $tasksResponse = $client->tasks->create($tasksRequest); +} catch (ApiExceptionInterface | ClientExceptionInterface $exception) { + echo $exception; // Every ApiExceptionInterface instance should implement __toString() method. + exit(-1); +} + +echo 'Created task with ID: ' . $tasksResponse->id; +``` + + +The error handling in the examples above is good enough for real production usage. +You can safely assume that `ApiExceptionInterface` is an error from the API, and `ClientExceptionInterface` is a client error +(e.g. network error or any runtime error, use `HttpClientException` to catch only PSR-18 client errors). +However, you can implement more complex error handling if you want. + +Also, both `ApiExceptionInterface` and `ClientExceptionInterface` implements `__toString()`. This means that you can just +convert those exceptions to string and put the results into logs without any special treatment for the exception data. + +More examples can be found in the [documentation](doc/usage/examples/index.md). + +You can use a PSR-14 compatible event dispatcher to receive events from the client. See [documentation](doc/index.md) for details. + +## Notes + +This library uses HTTPlug abstractions. Visit [official documentation](http://docs.php-http.org/en/latest/httplug/users.html) to learn more about it. diff --git a/bin/retailcrm-client b/bin/retailcrm-client new file mode 100755 index 0000000..84d4273 --- /dev/null +++ b/bin/retailcrm-client @@ -0,0 +1,49 @@ +#!/usr/bin/env php +isAbstract()) { + $application->add(new $commandFqn()); + } +} + +$application->setName('RetailCRM API Client Management Tool'); +$application->run(); diff --git a/composer.json b/composer.json index 481417e..bb07474 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,11 @@ "name": "retailcrm/api-client-php", "description": "PHP client for RetailCRM API", "type": "library", - "keywords": ["API", "RetailCRM", "REST"], + "keywords": [ + "API", + "RetailCRM", + "REST" + ], "homepage": "http://www.retailcrm.pro/", "license": "MIT", "authors": [ @@ -12,28 +16,101 @@ } ], "require": { - "php": ">=5.4.0", - "ext-curl": "*", + "php": ">=7.3.0", "ext-json": "*", - "ext-fileinfo": "*" + "psr/log": "^1.1", + "psr/http-client": "^1.0", + "psr/http-message": "^1.0", + "psr/http-message-implementation": "^1.0", + "php-http/client-implementation": "^1.0", + "php-http/message-factory": "^1.0", + "php-http/discovery": "^1.13", + "doctrine/annotations": "^1.11", + "liip/serializer": "^2.0", + "php-http/httplug": "^2.2", + "civicrm/composer-compile-plugin": "^0.15.0", + "symfony/console": "^4.0|^5.0", + "psr/event-dispatcher": "^1.0", + "neur0toxine/psr.http-client-implementation.php-http-curl": "*", + "neur0toxine/psr.http-factory-implementation.nyholm": "*", + "neur0toxine/psr.http-message-implementation.nyholm": "*", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": ">=v3.1.0" }, "require-dev": { - "phpunit/phpunit": "6.*", - "squizlabs/php_codesniffer": "3.*" + "php-http/mock-client": "^1.0", + "squizlabs/php_codesniffer": "^3.5", + "phpmd/phpmd": "^2.10", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^0.12.74", + "vlucas/phpdotenv": "^5.3", + "phpunit/phpunit": "^9.5", + "php-http/curl-client": "^2.2", + "nyholm/psr7": "^1.3", + "league/event": "^3.0", + "league/container": "^3.3", + "neur0toxine/pock": "^0.7" + }, + "suggest": { + "ext-curl": "Most HTTP clients need ext-curl to work properly.", + "php-http/client-implementation": "PSR-18 compatible client should be available to use this library.", + "psr/http-message-implementation": "PSR-7 compatible HTTP message implementation should be available to use to use this library.", + "psr/http-factory-implementation": "PSR-17 compatible factories should be available to use this library.", + "symfony/event-dispatcher": "PSR-14 compatible event dispatcher.", + "league/event": "PSR-14 compatible event dispatcher.", + "nyholm/psr7": "This is recommended PSR-7 and PSR-17 implementation.", + "php-http/curl-client": "Simplest PSR-18 client implementation.", + "symfony/http-client": "One of the most popular HTTP clients. Has PSR-18 compatible adapter.", + "psr/log-implementation": "You can use log implementation for debug purposes." + }, + "scripts": { + "phpunit": "./vendor/bin/phpunit -c phpunit.xml.dist --coverage-text", + "phpunit-ci": "@php -dpcov.enabled=1 -dpcov.directory=. -dpcov.exclude=\"~vendor~\" ./vendor/bin/phpunit --teamcity -c phpunit.xml.dist", + "phpmd": "./vendor/bin/phpmd src text ./phpmd.xml", + "phpcs": "./vendor/bin/phpcs -p src --runtime-set testVersion 7.3-8.0 && ./vendor/bin/phpcs -p tests --runtime-set testVersion 7.3-8.0 --warning-severity=0", + "phpstan": "./vendor/bin/phpstan analyse -c phpstan.neon src --memory-limit=-1", + "lint:fix": "./vendor/bin/phpcbf src", + "lint": [ + "@phpcs", + "@phpmd", + "@phpstan" + ], + "verify": [ + "@lint", + "@phpunit" + ], + "models": "@php bin/retailcrm-client models:generate --all" }, "support": { "email": "support@retailcrm.pro" }, "autoload": { - "psr-0": { "RetailCrm\\": "lib/" } + "psr-4": { + "RetailCrm\\Api\\": "src/" + } }, + "autoload-dev": { + "psr-4": { + "RetailCrm\\TestUtils\\": "tests/utils/", + "RetailCrm\\Tests\\": "tests/src/" + } + }, + "bin": [ + "bin/retailcrm-client" + ], "extra": { "branch-alias": { - "dev-master": "5.x-dev" - } + "dev-master": "6.x-dev" + }, + "compile": [ + { + "run": "@composer run-script models" + } + ] }, "config": { "bin-dir": "vendor/bin", "process-timeout": 600 } -} +} \ No newline at end of file diff --git a/doc/compilation_prompt.md b/doc/compilation_prompt.md new file mode 100644 index 0000000..a6a5d51 --- /dev/null +++ b/doc/compilation_prompt.md @@ -0,0 +1,83 @@ +## Compilation prompt + +After almost every Composer operation you will see this prompt: +```sh +The following packages have new compilation tasks: + - retailcrm/api-client-php has 1 task + +Allow these packages to compile? ([y]es, [a]lways, [n]o, [l]ist, [h]elp) +``` + +That's because the API client utilizes code generation to speed up the serialization and deserialization of the requests. However, +this prompt may be annoying and sometimes can even break the application lifecycle pipeline (in the CI/CD environment). We can't just +disable it for everyone [because of security concerns](https://github.com/composer/composer/issues/1193). But you can disable it for your project. + +There are two ways of disabling this prompt: +1. Automated way. +2. Manual way. + +### Disable or enable compilation prompt via CLI + +For the automated way, you can use `retailcrm-client` CLI utility. It will be in your binary directory. By default, it'll be in the +`vendor/bin` directory if not defined otherwise in the composer.json `config.bin-dir` entry. + +#### Disabling compilation prompt + +You can disable the compiler prompt by running this command: + +```sh +./vendor/bin/retailcrm-client compiler:prompt +``` + +Replace `vendor/bin` with your bin directory path if it's different from the default. + +You should see this message after that: +```sh + ✓ Done, generator prompt is now enabled. +``` + +#### Enabling compilation prompt + +If you want to revert this change and enable the compilation prompt then just run this command again with the `--activate` flag: + +```sh +./vendor/bin/retailcrm-client compiler:prompt --activate +``` + +### Disable or enable compilation prompt manually + +#### Enabling compilation prompt + +It is possible to replicate the same actions manually. Add these params into the `extra` segment of your `composer.json` if +you want to execute code generation automatically after library installation or update. + +```json +"compile-mode": "whitelist", +"compile-whitelist": ["retailcrm/api-client-php"] +``` + +Your `composer.json` file will look like this: +```json +{ + "name": "author/some-project", + "description": "Description of the project.", + "type": "project", + "license": "MIT", + "require": { + "php": ">=7.3.0", + "symfony/http-client": "^5.2", + "nyholm/psr7": "^1.4", + "retailcrm/api-client-php": "~6.0" + }, + "extra": { + "compile-mode": "whitelist", + "compile-whitelist": ["retailcrm/api-client-php"] + } +} +``` + +Voilà! You won't see the annoying prompt again. + +#### Enabling compilation prompt + +Just remove `extra.compile-mode` and `extra.compile-whitelist` params from your `composer.json`. diff --git a/doc/customization/customization.md b/doc/customization/customization.md new file mode 100644 index 0000000..f952d9d --- /dev/null +++ b/doc/customization/customization.md @@ -0,0 +1,19 @@ +## Customization + +* [Using different PSR-18, PSR-17 and PSR-7 implementations](different_psr_implementations.md) +* [Customizing request and response processing](pipelines/implementing_a_handler.md) + + [Using a predefined handler](pipelines/using_a_predefined_handler.md) + + [Built-in handlers](pipelines/using_a_predefined_handler.md#built-in-handlers) + + [Modifying the default pipeline](pipelines/using_a_predefined_handler.md#modifying-the-default-pipeline) + + [Constructing the pipeline from scratch](pipelines/using_a_predefined_handler.md#constructing-the-pipeline-from-scratch) + + [Implementing a handler](pipelines/implementing_a_handler.md) + +Both `ClientFactory` and `ClientBuilder` provide the necessary functionality to replace PSR dependencies with any other compatible implementation. +By default, those dependencies will be detected via service discovery. But service discovery supports a limited amount of implementation. +If your implementation is not supported - the client won't work unless you provide the necessary dependencies manually. +Another case would be testing. You can provide special HTTP client implementation which will return mocked responses instead of making +real requests. + +The Client uses [chain of responsibility](https://refactoring.guru/design-patterns/chain-of-responsibility) pattern to process requests. +Each request and response is being processed by the pipeline of handlers. Every handler can apply some mutation to the request or response +and can pass it to the next handler. In fact, API authentication is made possible by using a special handler which appends API key to every request. diff --git a/doc/customization/different_psr_implementations.md b/doc/customization/different_psr_implementations.md new file mode 100644 index 0000000..d4960a1 --- /dev/null +++ b/doc/customization/different_psr_implementations.md @@ -0,0 +1,69 @@ +## Controlling HTTP abstraction layer + +You can replace default PSR dependencies in the client while using `ClientFactory` or `ClientBuilder`. It can be useful for tests +or if you want to use a specific implementation that is not supported by the service discovery. + +Both `ClientFactory` and `ClientBuilder` provide those methods: + +```php +/** + * Set your PSR-18 HTTP client. + * + * Service discovery will be used if no client has been provided. + * + * @param \Psr\Http\Client\ClientInterface $httpClient + */ +public function setHttpClient(\Psr\Http\Client\ClientInterface $httpClient); + +/** + * Sets PSR-17 compatible stream factory. You can skip this step if you want to use service discovery. + * + * @param \Psr\Http\Message\StreamFactoryInterface|null $streamFactory + */ +public function setStreamFactory(?\Psr\Http\Message\StreamFactoryInterface $streamFactory); + +/** + * Sets PSR-17 compatible request factory. You can skip this step if you want to use service discovery. + * + * @param \Psr\Http\Message\RequestFactoryInterface|null $requestFactory + */ +public function setRequestFactory(?\Psr\Http\Message\RequestFactoryInterface $requestFactory); + +/** + * Sets PSR-17 compatible URI factory. You can skip this step if you want to use service discovery. + * + * @param \Psr\Http\Message\UriFactoryInterface|null $uriFactory + */ +public function setUriFactory(?\Psr\Http\Message\UriFactoryInterface $uriFactory); +``` + +They can be used to specify PSR dependencies like this: + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); +$factory = new \RetailCrm\Api\Factory\ClientFactory(); +$factory->setHttpClient(new \Http\Client\Curl\Client()) + ->setRequestFactory($psr17Factory) + ->setStreamFactory($psr17Factory) + ->setUriFactory($psr17Factory); + +$client = $factory->createClient('https://test.retailcrm.pro', 'apiKey'); +``` + +Or like this: + +```php +$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory(); +$builder = new \RetailCrm\Api\Builder\ClientBuilder(); +$client = $builder + ->setApiUrl('https://test.retailcrm.pro') + ->setAuthenticatorHandler(new \RetailCrm\Api\Handler\Request\HeaderAuthenticatorHandler('apiKey')) + ->setHttpClient(new \Http\Client\Curl\Client()) + ->setRequestFactory($psr17Factory) + ->setStreamFactory($psr17Factory) + ->setUriFactory($psr17Factory) + ->build(); +``` + +By replacing the HTTP client in the test environment you can easily mock requests and responses via libraries like +[`neur0toxine/pock`](https://packagist.org/packages/neur0toxine/pock) or [`php-http/mock-client`](https://packagist.org/packages/php-http/mock-client). diff --git a/doc/customization/pipelines/implementing_a_handler.md b/doc/customization/pipelines/implementing_a_handler.md new file mode 100644 index 0000000..4070b07 --- /dev/null +++ b/doc/customization/pipelines/implementing_a_handler.md @@ -0,0 +1,23 @@ +## Implementing a handler + +You can implement your own handler using the `RetailCrm\Api\Interfaces\HandlerInterface`. +`RetailCrm\Api\Handler\AbstractHandler` provides boilerplate code for the chain of responsibility. +`AbstractHandler::next` method will call next handler in the chain. You can safely use `return parent::next()` in your code +while using `AbstractHandler`. + +Most of the information about how handlers operate can be found in the [chain of responsibility](https://refactoring.guru/design-patterns/chain-of-responsibility) +pattern explanation. There are some specific details about handlers in the client. Client will pass desired dependencies to +the handler if handler implements one of those interfaces: + +* Any request handler + + `RetailCrm\Api\Interfaces\PsrFactoriesAwareInterface` will inject PSR-17 factories into the handler. +* Any response handler + + `RetailCrm\Api\Interfaces\SerializerAwareInterface` will inject a `liip/serializer` instance into the handler. + + `RetailCrm\Api\Interfaces\ApiExceptionFactoryAwareInterface` will inject a `Liip\Serializer\SerializerInterface` instance into the handler. + + `RetailCrm\Api\Interfaces\EventDispatcherAwareInterface` will inject a `Psr\EventDispatcher\EventDispatcherInterface` instance into the handler. + +All of those interfaces above have the corresponding implementation traits in the `RetailCrm\Api\Traits` namespace. +For example, `RetailCrm\Api\Interfaces\EventDispatcherAwareInterface` implementation can be found in the +`RetailCrm\Api\Traits\EventDispatcherAwareTrait` trait. + +Handlers can be used in the both chains just like callback handlers (see ["using a predefined hander"](using_a_predefined_handler.md)). diff --git a/doc/customization/pipelines/using_a_predefined_handler.md b/doc/customization/pipelines/using_a_predefined_handler.md new file mode 100644 index 0000000..918b908 --- /dev/null +++ b/doc/customization/pipelines/using_a_predefined_handler.md @@ -0,0 +1,292 @@ +## Using a predefined handler + +* [Built-in handlers](#built-in-handlers) +* [Modifying the default pipeline](#modifying-the-default-pipeline) +* [Constructing the pipeline from scratch](#constructing-the-pipeline-from-scratch) + +### Built-in handlers + +You can use a predefined handler to modify the process of request or response processing. Most of the predefined handlers are +already in use by the client, except for the `CallbackRequestHandler` and `CallbackResponseHandler`. + +Those are handlers that are present in the client and can be used for request processing: +* `CallbackRequestHandler` - processes a request using provided callback. +* `GetParameterAuthenticatorHandler` - appends a GET `apiKey` parameter with the API key. +* `HeaderAuthenticatorHandler` - appends `X-Api-Key` header with the API key. +* `PsrRequestHandler` - constructs base PSR-7 request with method and URI. +* `RequestDataHandler` - fills base PSR-7 request with the data from the DTO. + +Request handlers are using `RetailCrm\Api\Model\RequestData` DTO to share state between handlers. + +Those are handlers that can be used for response processing: +* `AccountNotFoundHandler` - detects when RetailCRM with specified API URL is not exist and throws specified exception. +* `CallbackResponseHandler` - processes a response using provided callback. +* `ErrorResponseHandler` - detects an error response, throws correct exception. +* `FilesDownloadResponseHandler` - handles `/api/v5/files/download` response. +* `UnmarshalResponseHandler` - unmarshals response body into response DTO. + +Response handlers are using `RetailCrm\Api\Model\ResponseData` DTO to share state between handlers. + +There are two built-in handlers which are most useful to users who want to modify the processing pipelines: `CallbackRequestHandler` and +`CallbackResponseHandler`. Both handlers use provided callbacks to modify a request or response. You can initialize those +with the examples below: + +```php +use RetailCrm\Api\Handler\Request\CallbackRequestHandler; +use RetailCrm\Api\Model\RequestData; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\UriFactoryInterface; + +$handler = new CallbackRequestHandler( + static function ( + RequestData $requestData, + RequestFactoryInterface $requestFactory, + StreamFactoryInterface $streamFactory, + UriFactoryInterface $uriFactory + ) { + if (null !== $requestData->request) { + $requestData->request = $requestData->request->withHeader('X-Rlimit-Token', 'example_token'); + } + } +); +``` + +```php +use RetailCrm\Api\Exception\Api\MissingParameterException; +use RetailCrm\Api\Factory\ApiExceptionFactory; +use RetailCrm\Api\Handler\Response\CallbackResponseHandler; +use RetailCrm\Api\Model\Response\Api\Credentials; +use RetailCrm\Api\Model\Response\ErrorResponse; +use RetailCrm\Api\Model\ResponseData; +use Liip\Serializer\SerializerInterface; +use Psr\EventDispatcher\EventDispatcherInterface; + +$handler = new CallbackResponseHandler( + static function ( + ResponseData $data, + SerializerInterface $serializer, + EventDispatcherInterface $eventDispatcher, + ApiExceptionFactory $apiExceptionFactory + ) { + if ( + $data->responseModel instanceof Credentials && + !in_array('/api/customers/create', $data->responseModel->credentials) + ) { + $data->responseModel = new ErrorResponse(); + $data->responseModel->errorMsg = 'Parameter "/api/customers/create" is missing'; + + throw new MissingParameterException($data->responseModel, 400); + } + } +); +``` + +Both handlers above can be appended to request and response pipelines. Let's move on to the next part to learn how +you can append handlers to the chain using `ClientFactory` or `ClientBuilder`. + +### Modifying the default pipeline + +Two limitations apply to default pipeline modifying: +1. Chain ordering cannot be changed. Your handler will always be at the end of the default chain. +1. Callback handlers will always call the next handler. There is no way to stop the next handlers in the chain. + +However, you can construct your own pipeline which won't be bound by the first limitation. And the second limitation can be +circumvented by [implementing your own handler](implementing_a_handler.md). + +Both `ClientFactory` and `ClientBuilder` allow you to append your own handlers to the processing pipeline. They provide +those methods: + +```php +/** + * Appends an additional request handler into the request processing chain. + * + * @param \RetailCrm\Api\Interfaces\HandlerInterface $handler + */ +public function appendRequestHandler(HandlerInterface $handler); + +/** + * Appends an additional handler into the response processing chain. + * + * @param \RetailCrm\Api\Interfaces\HandlerInterface $handler + */ +public function appendResponseHandler(HandlerInterface $handler); + +/** + * Appends an additional request handlers into the request processing chain. + * + * @param \RetailCrm\Api\Interfaces\HandlerInterface[] $handlers + */ +public function appendRequestHandlers(array $handlers); + +/** + * Appends an additional response handlers into the response processing chain. + * + * @param \RetailCrm\Api\Interfaces\HandlerInterface[] $handlers + */ +public function appendResponseHandlers(array $handlers); +``` + +You can use those methods to pass the handler into desired chain. Take a look at the examples. This code will initialize the client +with provided `CallbackRequestHandler`. Instantiation will be done via `ClientFactory`: + +```php +use RetailCrm\Api\Factory\ClientFactory; +use RetailCrm\Api\Handler\Request\CallbackRequestHandler; +use RetailCrm\Api\Model\RequestData; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\UriFactoryInterface; + +$handler = new CallbackRequestHandler( + static function ( + RequestData $requestData, + RequestFactoryInterface $requestFactory, + StreamFactoryInterface $streamFactory, + UriFactoryInterface $uriFactory + ) { + if (null !== $requestData->request) { + $requestData->request = $requestData->request->withHeader('X-Rlimit-Token', 'example_token'); + } + } +); + +$factory = new ClientFactory(); +$client = $factory->appendRequestHandler($handler)->createClient('https://test.retailcrm.pro', 'apiKey'); +``` + +And this code will instantiate a client using `ClientBuilder`. But this time we're modifying response pipeline: + +```php +use RetailCrm\Api\Builder\ClientBuilder; +use RetailCrm\Api\Builder\FormEncoderBuilder; +use RetailCrm\Api\Exception\Api\MissingParameterException; +use RetailCrm\Api\Factory\ApiExceptionFactory; +use RetailCrm\Api\Handler\Request\HeaderAuthenticatorHandler; +use RetailCrm\Api\Handler\Response\CallbackResponseHandler; +use RetailCrm\Api\Model\Response\Api\Credentials; +use RetailCrm\Api\Model\Response\ErrorResponse; +use RetailCrm\Api\Model\ResponseData; +use Liip\Serializer\SerializerInterface; +use Psr\EventDispatcher\EventDispatcherInterface; + +$handler = new CallbackResponseHandler( + static function ( + ResponseData $data, + SerializerInterface $serializer, + EventDispatcherInterface $eventDispatcher, + ApiExceptionFactory $apiExceptionFactory + ) { + if ( + $data->responseModel instanceof Credentials && + !in_array('/api/customers/create', $data->responseModel->credentials) + ) { + $data->responseModel = new ErrorResponse(); + $data->responseModel->errorMsg = 'Parameter "/api/customers/create" is missing'; + + throw new MissingParameterException($data->responseModel, 400); + } + } +); + +$formEncoder = (new FormEncoderBuilder())->setCacheDir('cache')->build(); +$builder = new ClientBuilder(); +$client = $builder->setApiUrl('https://test.retailcrm.pro') + ->setAuthenticatorHandler(new HeaderAuthenticatorHandler('apiKey')) + ->setFormEncoder($formEncoder) + ->appendRequestHandler($handler) + ->build(); +``` + +That's how you can modify request and response processing chains. However, you also can use the default pipeline factory +to pass desired handlers into the chain. Default request and response pipelines are constructed by those static factories: +```php +RetailCrm\Api\Factory\RequestPipelineFactory::createDefaultPipeline() +RetailCrm\Api\Factory\ResponsePipelineFactory::createDefaultPipeline() +``` + +Look at the example below to learn how to use those static factories. In this example we will modify both the request +and response pipelines: + +```php +use Liip\Serializer\SerializerInterface; +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\UriFactoryInterface; +use RetailCrm\Api\Builder\ClientBuilder; +use RetailCrm\Api\Builder\FormEncoderBuilder; +use RetailCrm\Api\Component\Transformer\RequestTransformer; +use RetailCrm\Api\Component\Transformer\ResponseTransformer; +use RetailCrm\Api\Exception\Api\MissingParameterException; +use RetailCrm\Api\Factory\ApiExceptionFactory; +use RetailCrm\Api\Factory\RequestPipelineFactory; +use RetailCrm\Api\Factory\ResponsePipelineFactory; +use RetailCrm\Api\Handler\Request\CallbackRequestHandler; +use RetailCrm\Api\Handler\Request\HeaderAuthenticatorHandler; +use RetailCrm\Api\Handler\Response\CallbackResponseHandler; +use RetailCrm\Api\Model\RequestData; +use RetailCrm\Api\Model\Response\Api\Credentials; +use RetailCrm\Api\Model\Response\ErrorResponse; +use RetailCrm\Api\Model\ResponseData; + +$requestHandler = new CallbackRequestHandler( + static function ( + RequestData $requestData, + RequestFactoryInterface $requestFactory, + StreamFactoryInterface $streamFactory, + UriFactoryInterface $uriFactory + ) { + if (null !== $requestData->request) { + $requestData->request = $requestData->request->withHeader('X-Rlimit-Token', 'example_token'); + } + } +); + +$responseHandler = new CallbackResponseHandler( + static function ( + ResponseData $data, + SerializerInterface $serializer, + EventDispatcherInterface $eventDispatcher, + ApiExceptionFactory $apiExceptionFactory + ) { + if ( + $data->responseModel instanceof Credentials && + !in_array('/api/customers/create', $data->responseModel->credentials) + ) { + $data->responseModel = new ErrorResponse(); + $data->responseModel->errorMsg = 'Parameter "/api/customers/create" is missing'; + + throw new MissingParameterException($data->responseModel, 400); + } + } +); + +$builder = new ClientBuilder(); +$formEncoder = (new FormEncoderBuilder())->setCacheDir('cache')->build(); +$client = $builder->setApiUrl('https://test.retailcrm.pro') + ->setAuthenticatorHandler(new HeaderAuthenticatorHandler('apiKey')) + ->setRequestTransformer(new RequestTransformer( + RequestPipelineFactory::createDefaultPipeline( + $formEncoder, + null, // PSR factories will be found by the service discovery. + null, + null, + $requestHandler + ) + )) + ->setResponseTransformer(new ResponseTransformer( + ResponsePipelineFactory::createDefaultPipeline( + $formEncoder->getSerializer(), + new ApiExceptionFactory(), + null, // No EventDispatcherInterface was provided + $responseHandler + ) + ))->build(); +``` + +### Constructing the pipeline from scratch + +Both `RequestTransformer` and `ResponseTransformer` accept the `HandlerInterface` instance as the first argument. You can +look into `RequestPipelineFactory` and `ResponsePipelineFactory` source code to learn how to build your own pipeline from +scratch. This is, however, is discouraged. diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 0000000..33e0862 --- /dev/null +++ b/doc/index.md @@ -0,0 +1,29 @@ +# Documentation + +* [Compilation prompt](compilation_prompt.md) + + [Disable or enable compilation prompt via CLI](compilation_prompt.md#disable-or-enable-compilation-prompt-via-cli) + + [Disable or enable compilation prompt manually](compilation_prompt.md#disable-or-enable-compilation-prompt-manually) +* [Client structure](structure.md) + + [Design principles](structure.md#design-principles) + + [Resource groups](structure.md#resource-groups) +* [Usage](usage/usage.md) + + [Instantiation](usage/instantiation.md) + + [Sending a request](usage/sending_a_request.md) + + [Choosing correct resource group, DTOs, and method](usage/sending_a_request.md#choosing-correct-resource-group-dtos-and-method) + + [Sending a request](usage/sending_a_request.md#sending-a-request) + + [Dealing with exceptions](usage/sending_a_request.md#dealing-with-exceptions) + + [Error handling](usage/error_handling.md) + + [Examples](usage/examples) + + [How to create an order](usage/examples/create_order.md) + + [How to receive the list of orders](usage/examples/fetch_orders.md) + + [How to handle all Client's exceptions](usage/examples/complete_error_handling_example.md) + + [Event handling](usage/event_handing.md) +* [Customization](customization/customization.md) + + [Controlling HTTP abstraction layer](customization/different_psr_implementations.md) + + [Customizing request and response processing](customization/pipelines/implementing_a_handler.md) + + [Using a predefined handler](customization/pipelines/using_a_predefined_handler.md) + + [Built-in handlers](customization/pipelines/using_a_predefined_handler.md#built-in-handlers) + + [Modifying the default pipeline](customization/pipelines/using_a_predefined_handler.md#modifying-the-default-pipeline) + + [Constructing the pipeline from scratch](customization/pipelines/using_a_predefined_handler.md#constructing-the-pipeline-from-scratch) + + [Implementing a handler](customization/pipelines/implementing_a_handler.md) +* [Troubleshooting](troubleshooting.md) diff --git a/doc/structure.md b/doc/structure.md new file mode 100644 index 0000000..bdf1b25 --- /dev/null +++ b/doc/structure.md @@ -0,0 +1,45 @@ +## Client structure + +The client is separated into several resource groups, all of which are accessible through the Client's public properties. Each resource group is responsible for the corresponding API section. It is much easier to understand how the Client works by learning its design principles. + +### Design principles + +The general principles of the Client design are: +1. The Client itself doesn't contain any API methods implementations. +1. ResourceGroups implements all API methods. +1. Every method can accept from zero to several arguments. +1. URI path arguments (not the query string ones) are always passed as separate method arguments. +1. Query parameters are always passed in the form of the Request model. The same principle applies to the POST form data. +1. If the Request contains only one parameter - it'll probably accept it through the constructor. This doesn't apply to the filter requests, which don't have constructors at all. +1. All DTO's you'll ever need can be found in the `RetailCrm\Api\Model` namespace. +1. Every method has an example of the usage in its DocBlock. Yes, all of them. You can learn how to use any of the methods just by looking at the DocBlock help tooltip provided by your IDE. If it's not provided by your IDE - you'll need to look up how to configure it. It'll ease your work with this (and any other) library a lot. + +### Resource groups + +The resource groups list into which client is separated: + +* `api` +* `costs` +* `customFields` +* `customers` +* `customersCorporate` +* `delivery` +* `files` +* `integration` +* `loyalty` +* `orders` +* `packs` +* `payments` +* `references` +* `segments` +* `settings` +* `store` +* `tasks` +* `telephony` +* `users` +* `verification` +* `statistics` + +Every group implements corresponding API documentation block: +* [English](https://docs.retailcrm.pro/Developers/API/APIVersions/APIv5) +* [Русский](https://docs.retailcrm.ru/Developers/API/APIVersions/APIv5) diff --git a/doc/troubleshooting.md b/doc/troubleshooting.md new file mode 100644 index 0000000..aec38cf --- /dev/null +++ b/doc/troubleshooting.md @@ -0,0 +1,59 @@ +### I've got error that looks like this: `Cannot deserialize body: Type "RetailCrm\Api\Model\Response\Api\ApiVersionsResponse" is not known.` + +Run this command to fix the problem: + +```sh +composer compile --all +``` + +The details about that error can be found in the [documentation](compilation_prompt.md). + +### I've got "No Message Factories" error. + +This means that PSR-17 implementation you have is not supported by the service discovery, therefore, API client cannot find proper factories. +In order to use your own PSR-17 implementation you need to use `ClientBuilder`. Also, you can just install supported implementation which is +much easier: +```sh +composer require nyholm/psr7 +``` + +### I've got `Http\Discovery\Exception\DiscoveryFailedException` or any other error with message like "`Could not find resource using any discovery strategy`". + +That's because you don't have any supported PSR-18, PSR-7 or PSR-17 implementation available. This usually happens if you do have any implementation for those +standards, but it's not supported by service discovery. You can fix this easily by installing supported implementations. We recommend using `symfony/http-client` +and `nyholm/psr7`. Install those using this command: +```sh +composer require nyholm/psr7 symfony/http-client +``` + +Alternatively, you can use `ClientBuilder` which allows you to pass your own HTTP client and message & URI factories. + +### There are too many available exceptions! How do I catch them all? + +Every exception in the library implements either `ApiExceptionInterface` or `ClientExceptionInterface`. First will be thrown in case of any +errors from the API, and the second will be thrown in case of any problems with the client or network itself. Concrete exception types are meant +to be used to determine what exactly gone wrong while sending a request. + +Also, you can use PSR-14 compatible event dispatcher to handle some exceptions globally. The Client will send `FailureRequestEvent` in case of any exceptions. +You can call `FailureRequestEvent::suppressThrow()` to prevent client from throwing an exception. + +### I can't test my app in the CI because Composer fails while installing dependencies. + +That's because you should explicitly allow code generation for the library. Otherwise, Composer will ask you to run compilation task at runtime +which is not possible in the CI since it lacks the support for interactive input. + +You can allow code generation tasks without interactive approval. Just add parameters from the JSON below to +the `"extra"` key of your project's `composer.json` file. +```json +{ + "compile-mode": "whitelist", + "compile-whitelist": ["retailcrm/api-client-php"] +} +``` + +### How can I choose proper DTO class for my request? + +Request DTO's can be found in the `RetailCrm\Api\Model\Request` namespace. They are separated by the API resource groups. +For example, requests for interaction with customer entities can be found in the `RetailCrm\Api\Model\Request\Customers` namespace +and requests for operations with the orders can be found in the `RetailCrm\Api\Model\Request\Orders` namespace. Every request method +defines it's input and output types. Also, you can choose correct type for child entities by looking at type annotations. diff --git a/doc/usage/error_handling.md b/doc/usage/error_handling.md new file mode 100644 index 0000000..abcf278 --- /dev/null +++ b/doc/usage/error_handling.md @@ -0,0 +1,52 @@ +## Error handling + +The API Client exceptions are hierarchical. It means that groups of exceptions can be caught using more generic exception types. +Let's take a look at the exceptions hierarchy: + +- `ApiException` is an abstraction and will never be thrown by itself. + - `AccessDeniedException` is thrown if the `Access denied.` error string is received from the API. + - `AccountDoesNotExistException` is thrown if the RetailCRM account with a specified API URL does not exist. + - `ApiErrorException` is thrown in case of any generic API error which is not recognized as a more specific type. + - `InvalidCredentialsException` is thrown if the provided API key is not correct. + - `MissingCredentialsException` is thrown if no API key was provided (this can happen if you initialize the Client without an authenticator). + - `MissingParameterException` is thrown if `Parameter '*' is missing` error is returned from the API (`*` is any parameter name). + - `ValidationException` is thrown if request validation is failed on the server-side (incorrect request data). +- `ClientException` is an abstraction and will never be thrown by itself. + - `BuilderException` is thrown by any builder if something goes wrong. + - `HandlerException` is thrown by the handlers in the request / response processing chain if they encountered an error. + - `HttpClientException` is thrown if underlying HTTP client throws an exception. + +`ApiException` implements `ApiExceptionInterface` and `ClientException` implements `ClientExceptionInterface`. +It means that you can catch any exception in the `ApiException` leaf using `ApiExceptionInterface` as an exception type. +Also, you can catch any exception in the `ClientException` leaf using the `ClientExceptionInterface` type. + +`ClientExceptionInterface` and `ApiExceptionInterface` implements `__toString()`. The first one will return a string with an error +message and stack trace and the second one will print out the error message, stack trace, and error response data. + +Let's take a look at the example: +```php +use RetailCrm\Api\Factory\SimpleClientFactory; +use RetailCrm\Api\Interfaces\ApiExceptionInterface; +use RetailCrm\Api\Interfaces\ClientExceptionInterface; + +$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + +try { + $apiVersions = $client->api->apiVersions(); +} catch (ApiExceptionInterface | ClientExceptionInterface $exception) { + echo $exception; + return; +} + +echo 'Available API versions: ' . implode(', ', $apiVersions->versions); +``` + +All Client exceptions can be found in the `RetailCrm\Api\Exception` namespace. `ApiException` children reside in the `RetailCrm\Api\Exception\Api` namespace, +and `ClientException` children reside at the `RetailCrm\Api\Exception\Client` namespace. + +It is recommended to look at the [complete error handling example](examples/complete_error_handling_example.md). +The most useful case would be the error message generation for the client in your app, which can look like this: +- `InvalidCredentialsException` - show the `Invalid API key` message. +- `AccountDoesNotExistException` - show the `Incorrect RetailCRM URL` or `This RetailCRM instance does not exist` message. +- `AccessDeniedException` - show the `Please enable the /api/v5/... method for this key` message. +- and so on. diff --git a/doc/usage/event_handing.md b/doc/usage/event_handing.md new file mode 100644 index 0000000..9c7449b --- /dev/null +++ b/doc/usage/event_handing.md @@ -0,0 +1,105 @@ +## Events + +You can use a PSR-14 compatible event dispatcher to receive events from the client. +It may be useful if you want to process certain events with the same logic without duplicating calls to such code. +These events are provided by the library: +- `RetailCrm\Api\Event\SuccessRequestEvent` will be dispatched if the request was successful. +- `RetailCrm\Api\Event\FailureRequestEvent` will be dispatched if the request was not successful. It won't be dispatched if the request cannot be formed at all. + +Event API documentation can be found here: +* [`FailureRequestEvent`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Event-FailureRequestEvent.html) +* [`SuccessRequestEvent`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Event-SuccessRequestEvent.html) + +Here is an example of event handling with Symfony. We're using an empty Symfony 5.x project with annotations routing. `IndexController` +outputs `/api/credentials` response without any changes. We'll handle all exceptions inside this event listener which will allow us +to call the method without any `try...catch` blocks. + +**config/services.yml** +```yaml +services: + # ClientFactory definition. + RetailCrm\Api\Interfaces\ClientFactoryInterface: + class: 'RetailCrm\Api\Factory\ClientFactory' + calls: + - setCacheDir: ['%kernel.cache_dir%'] + - setEventDispatcher: ['@event_dispatcher'] + + # Event listener definition. This listener will suppress exceptions and log them. + App\EventListener\FailureRequestListener: + tags: + - { name: kernel.event_listener, event: RetailCrm\Api\Event\FailureRequestEvent, method: onRequestFailure } +``` + +**src/EventListener/FailureRequestListener.php** +```php +logger = $logger; + } + + public function onRequestFailure(FailureRequestEvent $event): void + { + $exception = $event->getException(); + + if ($exception instanceof ApiExceptionInterface) { + $event->suppressThrow(); + $this->logger->error(sprintf( + 'CRM URL "%s", API error: %s', + $event->getApiUrl(), + (string) $exception + )); + } + + if ($exception instanceof ClientExceptionInterface) { + $event->suppressThrow(); + $this->logger->error(sprintf( + 'CRM URL "%s", client error: %s, trace: %s', + $event->getApiUrl(), + $exception->getMessage(), + $exception->getTraceAsString() + )); + } + } +} +``` + +**src/Controller/IndexController.php** +```php +createClient('https://test3487687.retailcrm.pro', 'key'); + $credentials = $client->api->credentials(); + + // Will print out empty model because https://test3487687.retailcrm.pro account does not exist. + return $this->json($credentials); + } +} +``` diff --git a/doc/usage/examples/complete_error_handling_example.md b/doc/usage/examples/complete_error_handling_example.md new file mode 100644 index 0000000..1e5a929 --- /dev/null +++ b/doc/usage/examples/complete_error_handling_example.md @@ -0,0 +1,115 @@ +Here is a complete example of the Client usage with error handling. It fetches the filtered orders data and prints it out. + +**Note:** it's not recommended using this example to process all API client errors in your application. It will clutter your code too much. +There are other options for exception processing. You can [match more generic exceptions in the hierarchy](../error_handling.md) or use +[events](../event_handing.md) to process exceptions. + +```php +filter = new OrderFilter(); +$request->filter->ids = [7141]; + +try { + $response = $client->orders->list($request); +} catch (HandlerException $exception) { + echo 'Error while trying to prepare request: ' . $exception->getMessage(); + exit(-1); +} catch (HttpClientException $exception) { + $prefix = 'Unknown error'; + + if ($exception->getPrevious() instanceof NetworkExceptionInterface) { + $prefix = 'Network error'; + } + + if ($exception->getPrevious() instanceof RequestExceptionInterface) { + $prefix = 'Invalid request'; + } + + if ($exception->getPrevious() instanceof PsrClientExceptionInterface) { + $prefix = 'HTTP client exception'; + } + + echo $prefix . ': ' . $exception->getMessage(); + + exit(-1); +} catch ( + InvalidCredentialsException | + AccessDeniedException | + AccountDoesNotExistException | + MissingCredentialsException | + MissingParameterException $exception + ) { + echo $exception->getMessage(); + exit(-1); +} catch (ValidationException $exception) { + echo 'Errors in fields:' . PHP_EOL; + + foreach ($exception->getErrorResponse()->errors as $field => $error) { + printf(" - %s: %s\n", $field, $error); + } + + exit(-1); +} catch (ApiExceptionInterface | ClientExceptionInterface $exception) { + echo $exception; // Every ApiException and ClientException implements __toString() method + exit(-1); +} catch (Throwable $throwable) { + echo 'Unknown runtime exception: ' . $throwable->getMessage() . PHP_EOL; + echo $throwable->getTraceAsString(); + exit(-1); +} + +foreach ($response->orders as $order) { + printf("Order ID: %d\n", $order->id); + printf("First name: %s\n", $order->firstName); + printf("Last name: %s\n", $order->lastName); + printf("Patronymic: %s\n", $order->patronymic); + printf("Phone #1: %s\n", $order->phone); + printf("Phone #2: %s\n", $order->additionalPhone); + printf("E-Mail: %s\n", $order->email); + + if ($order->customer instanceof CustomerCorporate) { + echo "Customer type: corporate\n"; + } else { + echo "Customer type: individual\n"; + } + + foreach ($order->items as $item) { + echo PHP_EOL; + + printf("Product name: %s\n", $item->productName); + printf("Quantity: %d\n", $item->quantity); + printf("Initial price: %f\n", $item->initialPrice); + } + + echo PHP_EOL; + + printf("Discount: %f\n", $order->discountManualAmount); + printf("Total: %f\n", $order->totalSumm); + + echo PHP_EOL; +} +``` diff --git a/doc/usage/examples/complex_pagination/PaginatedRequestIterator.php b/doc/usage/examples/complex_pagination/PaginatedRequestIterator.php new file mode 100644 index 0000000..25cff7d --- /dev/null +++ b/doc/usage/examples/complex_pagination/PaginatedRequestIterator.php @@ -0,0 +1,253 @@ + + */ +class PaginatedRequestIterator implements Iterator +{ + /** @var \RetailCrm\Api\Client */ + private $client; + + /** @var array */ + private $items = []; + + /** @var int */ + private $position = 0; + + /** @var int */ + private $seek = 0; + + /** @var int */ + private $currentPage = 1; + + /** @var bool */ + private $fetchFailed = false; + + /** @var \Throwable|null */ + private $error; + + /** @var bool */ + private $finalized = false; + + /** @var \RetailCrm\Api\Interfaces\RequestInterface */ + private $request; + + /** @var callable */ + private $nextPageFunc; + + /** @var callable */ + private $sendResponseFunc; + + /** @var callable */ + private $extractBatchFunc; + + /** + * PaginatedRequestIterator constructor. + * + * @param \RetailCrm\Api\Client $client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + /** + * This request will be used in every iteration. + * + * @param \RetailCrm\Api\Interfaces\RequestInterface $request + * + * @return self + */ + public function setRequest(RequestInterface $request): self + { + $this->request = $request; + return $this; + } + + /** + * This function should modify provided request which will be used in the next iteration. + * + * These params will be supplied in this exact order: + * - The request instance. + * - The response instance. + * - Next page number. + * + * @param callable $nextPageFunc + * + * @return self + */ + public function setNextPageFunc(callable $nextPageFunc): self + { + $this->nextPageFunc = $nextPageFunc; + return $this; + } + + /** + * This function should send the request and return the response. + * + * These params will be supplied in this exact order: + * - The Client instance. + * - The request instance. + * + * @param callable $sendResponseFunc + * + * @return self + */ + public function setSendResponseFunc(callable $sendResponseFunc): self + { + $this->sendResponseFunc = $sendResponseFunc; + return $this; + } + + /** + * This function should extract the batch of objects from the response. + * + * These params will be supplied in this exact order: + * - The response instance. + * + * @param callable $extractBatchFunc + * + * @return PaginatedRequestIterator + */ + public function setExtractBatchFunc(callable $extractBatchFunc): PaginatedRequestIterator + { + $this->extractBatchFunc = $extractBatchFunc; + return $this; + } + + /** + * @inheritDoc + * @return mixed + */ + public function current() + { + return $this->items[$this->getPosition()]; + } + + /** + * @inheritDoc + */ + public function next(): void + { + ++$this->position; + } + + /** + * @inheritDoc + */ + public function key(): int + { + return $this->position; + } + + /** + * @inheritDoc + */ + public function valid(): bool + { + if (empty($this->items) || !array_key_exists($this->getPosition(), $this->items)) { + $this->fetchChunk(); + } + + return isset($this->items[$this->getPosition()]); + } + + /** + * @inheritDoc + */ + public function rewind(): void + { + $this->items = []; + $this->position = 0; + $this->seek = 0; + $this->currentPage = 1; + $this->error = null; + } + + /** + * @return bool + */ + public function isFetchFailed(): bool + { + return $this->fetchFailed; + } + + /** + * @return \Throwable|null + */ + public function getError(): ?Throwable + { + return $this->error; + } + + /** + * @return int + */ + private function getPosition(): int + { + return $this->position - $this->seek; + } + + /** + * fetchChunk obtains chunk of data + */ + private function fetchChunk(): void + { + if ($this->finalized || $this->fetchFailed) { + return; + } + + try { + if ($this->currentPage > 1) { + time_nanosleep(0, 100000000); + } + + $response = call_user_func($this->sendResponseFunc, $this->client, $this->request); + + if ($response instanceof AbstractPaginatedResponse) { + $batch = call_user_func($this->extractBatchFunc, $response); + + if (empty($batch)) { + $this->items = []; + $this->fetchFailed = true; + $this->error = new HandlerException('Received an empty response'); + } else { + $this->seek += count($this->items); + $this->items = array_values($batch); + + if ($this->currentPage < $response->pagination->totalPageCount) { + call_user_func($this->nextPageFunc, $this->request, $response, ++$this->currentPage); + } else { + $this->finalized = true; + } + } + } + } catch (Throwable $exception) { + if ($exception instanceof ApiExceptionInterface && 503 === $exception->getStatusCode()) { + time_nanosleep(1, 0); + $this->fetchChunk(); + + return; + } + + $this->fetchFailed = true; + $this->error = $exception; + } + } +} diff --git a/doc/usage/examples/complex_pagination/example_customers_history.php b/doc/usage/examples/complex_pagination/example_customers_history.php new file mode 100644 index 0000000..cb4abe8 --- /dev/null +++ b/doc/usage/examples/complex_pagination/example_customers_history.php @@ -0,0 +1,39 @@ +filter = new CustomerHistoryFilter(); +$request->filter->startDate = (new DateTime())->sub(new DateInterval('P7D')); // 7 days +$request->limit = PaginationLimit::LIMIT_100; + +$paginator = (new PaginatedRequestIterator($client)) + ->setRequest($request) + ->setNextPageFunc(static function (CustomersHistoryRequest $request, CustomersHistoryResponse $response, int $page) { + $request->filter->startDate = null; + $request->filter->sinceId = end($response->history)->id; + }) + ->setExtractBatchFunc(static function (CustomersHistoryResponse $response) { + return $response->history; + }) + ->setSendResponseFunc(static function (Client $client, CustomersHistoryRequest $request) { + return $client->customers->history($request); + }); + +/** @var \RetailCrm\Api\Model\Entity\Customers\Customer $customer */ +foreach ($paginator as $historyEntry) { + print_r($historyEntry); +} + +if ($paginator->isFetchFailed()) { + echo $paginator->getError(); +} diff --git a/doc/usage/examples/complex_pagination/example_customers_list.php b/doc/usage/examples/complex_pagination/example_customers_list.php new file mode 100644 index 0000000..73ec6e3 --- /dev/null +++ b/doc/usage/examples/complex_pagination/example_customers_list.php @@ -0,0 +1,47 @@ +limit = PaginationLimit::LIMIT_100; +$request->page = 1; + +$paginator = (new PaginatedRequestIterator($client)) + ->setRequest($request) + ->setNextPageFunc(static function (CustomersRequest $request, CustomersResponse $response, int $page) { + $request->page = $page; + }) + ->setExtractBatchFunc(static function (CustomersResponse $response) { + return $response->customers; + }) + ->setSendResponseFunc(static function (Client $client, CustomersRequest $request) { + return $client->customers->list($request); + }); + +/** @var \RetailCrm\Api\Model\Entity\Customers\Customer $customer */ +foreach ($paginator as $customer) { + printf( + '%d: %s %s %s <%s> (%s) %s', + $customer->id, + $customer->firstName, + $customer->lastName, + $customer->patronymic, + implode(', ', array_map(static function (CustomerPhone $phone) { + return $phone->number; + }, $customer->phones)), + $customer->email, PHP_EOL + ); +} + +if ($paginator->isFetchFailed()) { + echo $paginator->getError(); +} diff --git a/doc/usage/examples/complex_pagination/index.md b/doc/usage/examples/complex_pagination/index.md new file mode 100644 index 0000000..a1efbbf --- /dev/null +++ b/doc/usage/examples/complex_pagination/index.md @@ -0,0 +1,8 @@ +Here are some complex examples of pagination handling. In this case, we're using a special component that handles the pagination for us. +As a result, we can just iterate over batches of the data using `foreach`. + +More than that, this component automatically handles rate limit exceptions. + +* [Fetching customers](example_customers_list.php) +* [Fetching customers history](example_customers_history.php) +* [Pagination component (`PaginatedRequestIterator`)](PaginatedRequestIterator.php) diff --git a/doc/usage/examples/create_order.md b/doc/usage/examples/create_order.md new file mode 100644 index 0000000..2ef34c4 --- /dev/null +++ b/doc/usage/examples/create_order.md @@ -0,0 +1,104 @@ +That's how you can create a new order: + +```php +type = 'bank-card'; +$payment->status = 'paid'; +$payment->amount = 1000; +$payment->paidAt = new DateTime(); + +$deliveryAddress->index = '344001'; +$deliveryAddress->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; +$deliveryAddress->region = 'Region'; +$deliveryAddress->city = 'City'; +$deliveryAddress->street = 'Street'; +$deliveryAddress->building = '10'; + +$delivery->address = $deliveryAddress; +$delivery->cost = 0; +$delivery->netCost = 0; + +$offer->name = 'Offer №1445123'; +$offer->displayName = 'Offer №1445123'; +$offer->xmlId = 'tGunLo27jlPGmbA8BrHxY2'; +$offer->article = '14451445-14451445'; +$offer->unit = new Unit('796', 'Piece', 'pcs'); + +$item->offer = $offer; +$item->priceType = new PriceType('base'); +$item->quantity = 1; +$item->purchasePrice = 60; + +$order->delivery = $delivery; +$order->items = [$item]; +$order->payments = [$payment]; +$order->orderType = 'test'; +$order->orderMethod = 'phone'; +$order->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; +$order->firstName = 'Test'; +$order->lastName = 'User'; +$order->patronymic = 'Patronymic'; +$order->phone = '89003005069'; +$order->email = 'testuser12345678901@example.com'; +$order->managerId = 28; +$order->customer = SerializedRelationCustomer::withIdAndType( + 4924, + CustomerType::CUSTOMER +); +$order->status = 'assembling'; +$order->statusComment = 'Assembling order'; +$order->weight = 1000; +$order->shipmentStore = 'main12'; +$order->shipmentDate = (new DateTime())->add(new DateInterval('P7D')); +$order->shipped = false; +$order->customFields = [ + "galka" => false, + "test_number" => 0, + "otpravit_dozakaz" => false, +]; + +$request->order = $order; +$request->site = 'moysklad'; + +try { + $response = $client->orders->create($request); +} catch (ApiExceptionInterface | ClientExceptionInterface $exception) { + echo $exception; // Every ApiExceptionInterface instance should implement __toString() method. + exit(-1); +} + +printf( + 'Created order id = %d with the following data: %s', + $response->id, + print_r($response->order, true) +); +``` + + diff --git a/doc/usage/examples/fetch_orders.md b/doc/usage/examples/fetch_orders.md new file mode 100644 index 0000000..8dfec01 --- /dev/null +++ b/doc/usage/examples/fetch_orders.md @@ -0,0 +1,50 @@ +That's how you can fetch the orders list: + +```php +orders->list(); +} catch (ApiExceptionInterface | ClientExceptionInterface $exception) { + echo $exception; // Every ApiExceptionInterface instance should implement __toString() method. + exit(-1); +} + +foreach ($response->orders as $order) { + printf("Order ID: %d\n", $order->id); + printf("First name: %s\n", $order->firstName); + printf("Last name: %s\n", $order->lastName); + printf("Patronymic: %s\n", $order->patronymic); + printf("Phone #1: %s\n", $order->phone); + printf("Phone #2: %s\n", $order->additionalPhone); + printf("E-Mail: %s\n", $order->email); + + if ($order->customer instanceof CustomerCorporate) { + echo "Customer type: corporate\n"; + } else { + echo "Customer type: individual\n"; + } + + foreach ($order->items as $item) { + echo PHP_EOL; + + printf("Product name: %s\n", $item->productName); + printf("Quantity: %d\n", $item->quantity); + printf("Initial price: %f\n", $item->initialPrice); + } + + echo PHP_EOL; + + printf("Discount: %f\n", $order->discountManualAmount); + printf("Total: %f\n", $order->totalSumm); + + echo PHP_EOL; +} +``` diff --git a/doc/usage/examples/index.md b/doc/usage/examples/index.md new file mode 100644 index 0000000..4e7affa --- /dev/null +++ b/doc/usage/examples/index.md @@ -0,0 +1,7 @@ +## Examples + +* [How to create an order](create_order.md) +* [How to receive the list of orders](fetch_orders.md) +* [How to handle all Client's exceptions](complete_error_handling_example.md) +* [Fetching orders history](orders_history.md) +* [Complex pagination example](complex_pagination/index.md) diff --git a/doc/usage/examples/orders_history.md b/doc/usage/examples/orders_history.md new file mode 100644 index 0000000..cf9ee32 --- /dev/null +++ b/doc/usage/examples/orders_history.md @@ -0,0 +1,34 @@ +In this example we will fetch all history entries for 1 month from the API. + +```php +use RetailCrm\Api\Enum\PaginationLimit; +use RetailCrm\Api\Factory\SimpleClientFactory; +use RetailCrm\Api\Interfaces\ApiExceptionInterface; +use RetailCrm\Api\Interfaces\ClientExceptionInterface; +use RetailCrm\Api\Model\Filter\Orders\OrderHistoryFilterV4Type; +use RetailCrm\Api\Model\Request\Orders\OrdersHistoryRequest; + +$history = []; +$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); +$request = new OrdersHistoryRequest(); +$request->filter = new OrderHistoryFilterV4Type(); +$request->limit = PaginationLimit::LIMIT_100; +$request->filter->startDate = (new DateTime())->sub(new DateInterval('P1M')); // History for 1 month by default. + +do { + time_nanosleep(0, 100000000); // 10 requests per second + + try { + $response = $client->orders->history($request); + } catch (ClientExceptionInterface | ApiExceptionInterface $exception) { + echo $exception; + break; + } + + $history = array_merge($history, $response->history); + $request->filter->startDate = null; + $request->filter->sinceId = end($response->history)->id; +} while ($response->pagination->currentPage < $response->pagination->totalPageCount); + +// Here you can process all history entries in the `$history` variable. +``` diff --git a/doc/usage/instantiation.md b/doc/usage/instantiation.md new file mode 100644 index 0000000..e1cfd07 --- /dev/null +++ b/doc/usage/instantiation.md @@ -0,0 +1,255 @@ +## Instantiation + +There are several ways to instantiate the Client. Let's take look at them: from the simplest one to the most complex. + +* [Instantiation via `SimpleClientFactory`](#instantiation-via-simpleclientfactory) +* [Instantiation via `ClientFactory`](#instantiation-via-clientfactory) + + [Simple example](#simple-example) + + [Abstract example](#abstract-example) + + [Real-world example (Symfony)](#real-world-example-symfony) +* [Instantiation via `ClientBuilder`](#instantiation-via-clientbuilder) + +### Instantiation via `SimpleClientFactory` + +It's the easiest way to instantiate an API client. You can use this method if you just want to use API client without +thinking much about integration with the app or framework. + +Example: + +```php +use RetailCrm\Api\Factory\SimpleClientFactory; + +$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'key'); +``` + +That's it. You can use `$client` to make requests. However, even this simple method allows you to pass the `CacheItemPoolInterface` instance +(`symfony/cache` is being used by default) or path to the cache directory. The cache is being used by the client to store +annotations which omits the necessity to parse them each time you send a request. + +Example with the `CacheItemPoolInterface` instance (we're using Redis as cache here): + +```php +use Symfony\Component\Cache\Adapter\RedisAdapter; +use RetailCrm\Api\Enum\CacheDirectories; +use RetailCrm\Api\Factory\SimpleClientFactory; + +$redis = new Redis(); +$redis->connect('127.0.0.1', 6379); + +$client = SimpleClientFactory::createWithCache( + 'https://test.retailcrm.pro', + 'key', + new RedisAdapter($redis, CacheDirectories::DIR_NAME) +); +``` + +`CacheDirectories::DIR_NAME` is optional here. You can use any other namespace you want. By default, `CacheDirectories::DIR_NAME` +will be used as a namespace by default if the cache instance was created by the client automatically. + +It's easier to use `SimpleClientFactory::createWithCacheDir` if you just want to specify your directory for cache instead of +default temporary directory: + +```php +use RetailCrm\Api\Factory\SimpleClientFactory; + +$client = SimpleClientFactory::createWithCacheDir('https://test.retailcrm.pro', 'key', __DIR__ . '/cache'); +``` + +You can find more details about `SimpleClientFactory` in the documentation: +* [`SimpleClientFactory`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Factory-SimpleClientFactory.html) + +### Instantiation via `ClientFactory` + +The main difference between `SimpleClientFactory` and `ClientFactory` is the fact that the second factory is stateful. +It allows you to better integrate this client with your application. Consider the following: you may just set all global client +dependencies into the `ClientFactory` during the dependency container instantiation, and use the factory after that +to instantiate any number of clients you want. + +#### Simple example + +Here's an example of `ClientFactory` usage without any integration with the framework or the app: + +```php +use RetailCrm\Api\Factory\ClientFactory; +use League\Event\EventDispatcher; + +$eventDispatcher = new EventDispatcher(); +$factory = new ClientFactory(); +$factory + ->setCacheDir('/tmp/retailcrm_cache') + ->setEventDispatcher($eventDispatcher); + +$client = $factory->createClient('https://test.retailcrm.pro', 'key'); +``` + +Doesn't look that impressive, right? That's because `ClientFactory` is not supposed to be used just like that. +It won't give you any benefits in comparison to `SimpleClientFactory` with such usage. + +Let's take a look at the more complex example. We'll use `league/container` here to demonstrate how you can integrate +`ClientFactory` with the DI you're using. + +#### Abstract example + +Let's assume that you have a specific service that should instantiate a Client for some internal purposes. +Here it is: + +```php +namespace App\Services; + +use RetailCrm\Api\Interfaces\ClientFactoryInterface; +use Throwable; + +class ClientFactoryDependentService +{ + /** @var ClientFactoryInterface */ + private $clientFactory; + + public function __construct(ClientFactoryInterface $clientFactory) + { + $this->clientFactory = $clientFactory; + } + + public function isApiAccessible(string $apiUrl, string $apiKey): bool + { + try { + $client = $this->clientFactory->createClient($apiUrl, $apiKey); + $client->api->apiVersions(); + + return true; + } catch (Throwable $throwable) { + return false; + } + } +} +``` + +And you have a controller which returns a JSON response with the API availability flag. It requires the service above. + +```php +namespace App\Controller; + +use Some\Framework\Specific\Annotations\Route; +use Some\Framework\Specific\Controller; +use Some\Framework\Specific\Response; + +class HealthCheckController extends Controller +{ + /** @var ClientFactoryDependentService */ + private $service; + + public function __construct(ClientFactoryDependentService $service) + { + $this->service = $service; + } + + /** + * @Route("/healthcheck") + */ + public function healthCheckAction(): Response + { + return $this->responseJson([ + 'is_api_accessible' => $this->service->isApiAccessible($_GET['apiUrl'], $_GET['apiKey']) + ]); + } +} +``` + +During the container configuration you configure the container to instantiate proper `ClientFactory` once. After that +you can use the factory in any service you want. Here's an example with the `league/container`: + +```php +use RetailCrm\Api\Factory\ClientFactory; +use RetailCrm\Api\Interfaces\ClientFactoryInterface; +use League\Container\Container; +use League\Event\EventDispatcher; +use Psr\Cache\CacheItemPoolInterface; +use Psr\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Cache\Adapter\FilesystemAdapter; +use App\Services\ClientFactoryDependentService; +use App\Controller\HealthCheckController; + +$container = new Container(); + +$container->add(CacheItemPoolInterface::class, new FilesystemAdapter('test_app')); +$container->add(EventDispatcherInterface::class, EventDispatcher::class); +$container->add(ClientFactoryInterface::class, ClientFactory::class) + ->addMethodCalls([ + 'setCache' => [CacheItemPoolInterface::class], + 'setEventDispatcher' => [EventDispatcherInterface::class], + ]); +$container->add(ClientFactoryDependentService::class)->addArgument(ClientFactoryInterface::class); +$container->add(HealthCheckController::class)->addArgument(ClientFactoryDependentService::class); +``` + +That's it! Now your controller is using the underlying service to form a response. The service is using `ClientFactory` +which is being instantiated only once by the container. `ClientFactory` can be injected into other services as well, and it +will be the same instance. + +#### Real-world example (Symfony) + +You can use this exact service definition to instantiate a `ClientFactory` instance: + +```yaml +RetailCrm\Api\Interfaces\ClientFactoryInterface: + class: 'RetailCrm\Api\Factory\ClientFactory' + calls: + - setCacheDir: ['%kernel.cache_dir%'] + - setEventDispatcher: ['@event_dispatcher'] +``` + +That's it. Now you can autowire `ClientFactoryInterface` in your services. It also allows you to mock the factory itself +in the tests using `services_test.yml`. + +You can find more details about `ClientFactory` in the documentation: +* [`ClientFactory`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Factory-ClientFactory.html) + +### Instantiation via `ClientBuilder` + +`ClientBuilder` is the most powerful, and most complex method of client instantiation. It allows you to replace any client's +external dependencies with your own implementations. Here's an example of `ClientBuilder` usage: + +```php +use Http\Client\Curl\Client as CurlClient; +use League\Event\EventDispatcher; +use Nyholm\Psr7\Factory\Psr17Factory; +use RetailCrm\Api\Builder\ClientBuilder; +use RetailCrm\Api\Builder\FormEncoderBuilder; +use RetailCrm\Api\Component\Transformer\RequestTransformer; +use RetailCrm\Api\Component\Transformer\ResponseTransformer; +use RetailCrm\Api\Factory\ApiExceptionFactory; +use RetailCrm\Api\Factory\RequestPipelineFactory; +use RetailCrm\Api\Factory\ResponsePipelineFactory; +use RetailCrm\Api\Handler\Request\HeaderAuthenticatorHandler; + +$eventDispatcher = new EventDispatcher(); +$psr17Factory = new Psr17Factory(); +$httpClient = new CurlClient(); + +$builder = new ClientBuilder(); +$formEncoder = (new FormEncoderBuilder())->setCacheDir('cache')->build(); +$client = $builder->setApiUrl('https://test.retailcrm.pro') + ->setAuthenticatorHandler(new HeaderAuthenticatorHandler('apiKey')) + ->setFormEncoder($formEncoder) + ->setHttpClient($httpClient) + ->setRequestTransformer(new RequestTransformer( + RequestPipelineFactory::createDefaultPipeline( + $formEncoder, + $psr17Factory, // PSR-17 UriFactoryInterface + $psr17Factory, // PSR-17 RequestFactoryInterface + $psr17Factory // PSR-17 StreamFactoryInterface + ) + )) + ->setResponseTransformer(new ResponseTransformer( + ResponsePipelineFactory::createDefaultPipeline( + $formEncoder->getSerializer(), + new ApiExceptionFactory(), + $eventDispatcher + ) + ))->build(); +``` + +You can find more details about this in the documentation: +* [`ClientBuilder`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Builder-ClientBuilder.html) +* [`FormEncoderBuilder`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Builder-FormEncoderBuilder.html) +* [`RequestPipelineFactory`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Factory-RequestPipelineFactory.html) +* [`ResponsePipelineFactory`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Factory-ResponsePipelineFactory.html) diff --git a/doc/usage/sending_a_request.md b/doc/usage/sending_a_request.md new file mode 100644 index 0000000..0af9050 --- /dev/null +++ b/doc/usage/sending_a_request.md @@ -0,0 +1,144 @@ +## Sending a request + +You need three things to send a request: +1. [Resource group, DTOs, and method](#choosing-correct-resource-group-dtos-and-method) +2. [Send a request](#sending-a-request) +3. [Deal with exceptions](#dealing-with-exceptions) + +### Choosing correct resource group, DTOs, and method + +First, take a look at the API itself: +* [English](https://docs.retailcrm.pro/Developers/API/APIVersions/APIv5) +* [Русский](https://docs.retailcrm.ru/Developers/API/APIVersions/APIv5) + +Choose a method you want to use. Which one is yours depend on the task you want to perform. + +Then take look at the API again. It consists of several blocks, each block is responsible for a specific set of features. +The Client itself is also separated to those blocks or resource groups (full list of them can be found [here](../structure.md#resource-groups)). + +Each resource group corresponds to an equal block in the documentation. For example. `customersCorporate` resource group implements methods +in the _Corporate customers_ API block (рус. _Корпоративные клиенты_). + +Just look at the method you want to use and pick a proper resource group. That's it. Let's move on to the DTOs. + +Choosing proper DTOs is also easy. Each client method defines expected parameter types. If the method doesn't have any +parameters, then the API method is also doesn't require any parameters and can be just called. The most noteworthy would be +`/api/api-versions` and `/api/credentials` methods. They can be called from the Client instance like that: + +```php +use RetailCrm\Api\Factory\SimpleClientFactory; + +$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'key'); +$client->api->apiVersions(); +$client->api->credentials(); +``` + +The majority of the methods will require some parameters. Usually it's a request DTO, some path parameters (like customer id) +or both. For example, `/api/v5/customers/create` method requires request DTO, `/api/v5/customers/{externalId}` requires +customer ID and `/api/v5/customers/{externalId}/edit` requires both. + +The corresponding methods on the client instance are: + +| API method | Client instance method | +| ------------------------------------- | ---------------------------- | +| `/api/v5/customers/create` | `$client->customers->create` | +| `/api/v5/customers/{externalId}` | `$client->customers->get` | +| `/api/v5/customers/{externalId}/edit` | `$client->customers->edit` | + +Look at the method definitions (they have also shown by the IDE): + +```php +public function create(CustomersCreateRequest $request): IdResponse; +public function get($identifier, ?BySiteRequest $request = null): CustomersGetResponse; +public function edit($identifier, CustomersEditRequest $request): CustomersEditResponse; +``` + +Just use types from the type hints and everything will work. The DTO's fields also have type declarations but in the form +of the `@var` annotation tags. That's how you can choose the correct type for the DTO fields. + +### Sending a request + +Using information from the previous article you can easily construct a request. + +This request will create a new customer. + +```php +use RetailCrm\Api\Factory\SimpleClientFactory; +use RetailCrm\Api\Model\Entity\Customers\Customer; +use RetailCrm\Api\Model\Request\Customers\CustomersCreateRequest; + +$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'key'); +$request = new CustomersCreateRequest(); + +$request->customer = new Customer(); +$request->customer->email = 'test@example.com'; +$request->site = 'site'; + +$response = $client->customers->create($request); +``` + +This one will fetch specific customer from the API by the ID and site. + +```php +use RetailCrm\Api\Enum\ByIdentifier;use RetailCrm\Api\Factory\SimpleClientFactory; +use RetailCrm\Api\Model\Request\BySiteRequest; + +$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'key'); +$response = $client->customers->get(1, new BySiteRequest(ByIdentifier::ID, 'site')); + +echo $response->customer->email; +``` + +And this one will edit specific customer: + +```php +use RetailCrm\Api\Enum\ByIdentifier; +use RetailCrm\Api\Factory\SimpleClientFactory; +use RetailCrm\Api\Model\Entity\Customers\Customer; +use RetailCrm\Api\Model\Request\Customers\CustomersEditRequest; + +$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'key'); +$request = new CustomersEditRequest(); + +$request->customer = new Customer(); +$request->customer->email = 'test@example.com'; +$request->site = 'site'; +$request->by = ByIdentifier::ID; + +$response = $client->customers->edit(1, $request); + +echo "Edited customer ID: " . $response->id; +``` + +### Dealing with exceptions + +What will happen if an API error is returned for the one of requests above? Or the network is not working at all? +An exception will happen. + +There are two main exception types: +* `RetailCrm\Api\Interfaces\ApiExceptionInterface` is thrown if API returned an error. +* `RetailCrm\Api\Interfaces\ClientExceptionInterface` is thrown if the request cannot be sent due to an internal error. + +You can use those like this: + +```php +use RetailCrm\Api\Factory\SimpleClientFactory; +use RetailCrm\Api\Interfaces\ApiExceptionInterface; +use RetailCrm\Api\Interfaces\ClientExceptionInterface; + +$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'key'); + +try { + $response = $client->customers->list(); +} catch (ApiExceptionInterface $exception) { + echo $exception; +} catch (ClientExceptionInterface $exception) { + echo 'Client error: ' . $exception->getMessage(); +} + +if (isset($response)) { + echo "Customers: " . print_r($response->customers, true); +} +``` + +More information about exceptions can be found on the [error handling](error_handling.md) page. diff --git a/doc/usage/usage.md b/doc/usage/usage.md new file mode 100644 index 0000000..e9964e2 --- /dev/null +++ b/doc/usage/usage.md @@ -0,0 +1,15 @@ +## Usage + +* [Instantiation](instantiation.md) +* [Sending a request](sending_a_request.md) + * [Choosing correct resource group, DTOs, and method](sending_a_request.md#choosing-correct-resource-group-dtos-and-method) + * [Sending a request](sending_a_request.md#sending-a-request) + * [Dealing with exceptions](sending_a_request.md#dealing-with-exceptions) +* [Error handling](error_handling.md) +* [Examples](examples) + + [How to create an order](examples/create_order.md) + + [How to receive the list of orders](examples/fetch_orders.md) + + [How to handle all Client's exceptions](examples/complete_error_handling_example.md) + + [Fetching orders history](examples/orders_history.md) + + [Complex pagination example](examples/complex_pagination/index.md) +* [Event handling](event_handing.md) diff --git a/lib/RetailCrm/ApiClient.php b/lib/RetailCrm/ApiClient.php deleted file mode 100644 index 4149a5f..0000000 --- a/lib/RetailCrm/ApiClient.php +++ /dev/null @@ -1,70 +0,0 @@ -version = $version; - - switch ($version) { - case self::V4: - $this->request = new ApiVersion4($url, $apiKey, $version, $site, $debug); - break; - case self::V3: - $this->request = new ApiVersion3($url, $apiKey, $version, $site, $debug); - break; - default: - $this->request = new ApiVersion5($url, $apiKey, $version, $site, $debug); - break; - } - } - - /** - * Get API version - * - * @return string - */ - public function getVersion() - { - return $this->version; - } -} diff --git a/lib/RetailCrm/Client/AbstractLoader.php b/lib/RetailCrm/Client/AbstractLoader.php deleted file mode 100755 index be5128e..0000000 --- a/lib/RetailCrm/Client/AbstractLoader.php +++ /dev/null @@ -1,174 +0,0 @@ -crmUrl = $url; - - if (empty($version) || !in_array($version, ['v3', 'v4', 'v5'])) { - throw new \InvalidArgumentException( - 'Version must be not empty and must be equal one of v3|v4|v5' - ); - } - - $url = $url . 'api/' . $version; - - $this->client = new Client($url, ['apiKey' => $apiKey], $debug); - $this->siteCode = $site; - } - - /** - * Set request options - * - * @param RequestOptions $options - */ - public function setOptions(RequestOptions $options) - { - $this->client->setOptions($options); - } - - /** - * Set site - * - * @param string $site site code - * - * @return void - */ - public function setSite($site) - { - $this->siteCode = $site; - } - - /** - * Check ID parameter - * - * @param string $by identify by - * - * @throws \InvalidArgumentException - * - * @return bool - */ - protected function checkIdParameter($by) - { - $allowedForBy = [ - 'externalId', - 'id' - ]; - - if (!in_array($by, $allowedForBy, false)) { - throw new \InvalidArgumentException( - sprintf( - 'Value "%s" for "by" param is not valid. Allowed values are %s.', - $by, - implode(', ', $allowedForBy) - ) - ); - } - - return true; - } - - /** - * Fill params by site value - * - * @param string $site site code - * @param array $params input parameters - * - * @return array - */ - protected function fillSite($site, array $params) - { - if ($site) { - $params['site'] = $site; - } elseif ($this->siteCode) { - $params['site'] = $this->siteCode; - } - - return $params; - } - - /** - * Return current site - * - * @return string - */ - public function getSite() - { - return $this->siteCode; - } - - /** - * Getting the list of available api versions - * - * @return \RetailCrm\Response\ApiResponse - */ - public function availableVersions() - { - return $this->client->makeRequest( - $this->crmUrl . 'api/api-versions', - "GET", - [], - true - ); - } - - /** - * Getting the list of available api methods and stores for current key - * - * @return \RetailCrm\Response\ApiResponse - */ - public function credentials() - { - return $this->client->makeRequest( - $this->crmUrl . 'api/credentials', - "GET", - [], - true - ); - } -} diff --git a/lib/RetailCrm/Client/ApiVersion3.php b/lib/RetailCrm/Client/ApiVersion3.php deleted file mode 100644 index 91abf75..0000000 --- a/lib/RetailCrm/Client/ApiVersion3.php +++ /dev/null @@ -1,47 +0,0 @@ -url = $url; - $this->defaultParameters = $defaultParameters; - $this->options = new RequestOptions(); - } - - /** - * Make HTTP request - * - * @param string $path request url - * @param string $method (default: 'GET') - * @param array $parameters (default: array()) - * @param bool $fullPath (default: false) - * - * @SuppressWarnings(PHPMD.ExcessiveParameterList) - * - * @throws \InvalidArgumentException - * @throws CurlException - * @throws InvalidJsonException - * - * @return ApiResponse - */ - public function makeRawRequest( - $path, - $method, - array $parameters = [], - $fullPath = false - ) { - $allowedMethods = [self::METHOD_GET, self::METHOD_POST]; - - if (!in_array($method, $allowedMethods, false)) { - throw new \InvalidArgumentException( - sprintf( - 'Method "%s" is not valid. Allowed methods are %s', - $method, - implode(', ', $allowedMethods) - ) - ); - } - - $parameters = array_merge($this->defaultParameters, $parameters); - - $url = $fullPath ? $path : $this->url . $path; - - if (self::METHOD_GET === $method && count($parameters)) { - $url .= '?' . http_build_query($parameters, '', '&'); - } - - if (self::METHOD_POST === $method && '/files/upload' == $path) { - $url .= '?apiKey=' . $parameters['apiKey']; - } - - $curlHandler = curl_init(); - curl_setopt($curlHandler, CURLOPT_URL, $url); - curl_setopt($curlHandler, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curlHandler, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curlHandler, CURLOPT_FAILONERROR, false); - curl_setopt($curlHandler, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($curlHandler, CURLOPT_SSL_VERIFYHOST, false); - curl_setopt($curlHandler, CURLOPT_TIMEOUT, $this->options->getClientTimeout()); - curl_setopt($curlHandler, CURLOPT_CONNECTTIMEOUT, $this->options->getClientTimeout()); - - if ($this->options->getHeaders()) { - curl_setopt($curlHandler, CURLOPT_HTTPHEADER, $this->options->getHttpHeaders()); - } - - if (self::METHOD_POST === $method) { - curl_setopt($curlHandler, CURLOPT_POST, true); - - if ('/files/upload' == $path) { - curl_setopt($curlHandler, CURLOPT_POSTFIELDS, file_get_contents($parameters['file'])); - } else { - curl_setopt($curlHandler, CURLOPT_POSTFIELDS, $parameters); - } - } - - list($statusCode, $responseBody) = $this->checkResponse($curlHandler, $method); - - return new ApiResponse($statusCode, $responseBody); - } - - /** - * Make HTTP request and deserialize JSON body (throws exception otherwise) - * - * @param string $path request url - * @param string $method (default: 'GET') - * @param array $parameters (default: array()) - * @param bool $fullPath (default: false) - * - * @SuppressWarnings(PHPMD.ExcessiveParameterList) - * - * @throws \InvalidArgumentException - * @throws CurlException - * @throws InvalidJsonException - * - * @return ApiResponse - */ - public function makeRequest( - $path, - $method, - array $parameters = [], - $fullPath = false - ) { - return $this->makeRawRequest($path, $method, $parameters, $fullPath)->asJsonResponse(); - } - - /** - * Set request options - * - * @param RequestOptions $options - */ - public function setOptions(RequestOptions $options) - { - $this->options = $options; - } - - /** - * @param $curlHandler - * @param $method - * - * @return array - */ - private function checkResponse($curlHandler, $method) - { - $responseBody = curl_exec($curlHandler); - $statusCode = curl_getinfo($curlHandler, CURLINFO_HTTP_CODE); - $contentType = curl_getinfo($curlHandler, CURLINFO_CONTENT_TYPE); - - if (503 === $statusCode) { - throw new LimitException("Service temporary unavailable"); - } - - if ( - (404 === $statusCode && false !== stripos($responseBody, 'Account does not exist')) - || ('GET' !== $method && 405 === $statusCode && false !== stripos($contentType, 'text/html')) - ) { - throw new NotFoundException("Account does not exist"); - } - - $errno = curl_errno($curlHandler); - $error = curl_error($curlHandler); - - curl_close($curlHandler); - - if ($errno) { - throw new CurlException($error, $errno); - } - - return [$statusCode, $responseBody]; - } -} diff --git a/lib/RetailCrm/Http/RequestOptions.php b/lib/RetailCrm/Http/RequestOptions.php deleted file mode 100644 index d669754..0000000 --- a/lib/RetailCrm/Http/RequestOptions.php +++ /dev/null @@ -1,95 +0,0 @@ -headers = $headers; - $this->clientTimeout = $clientTimeout; - } - - /** - * @return array - */ - public function getHeaders() - { - return $this->headers; - } - - /** - * @return array - */ - public function getHttpHeaders() - { - $headers = []; - - foreach ($this->headers as $header => $value) { - $headers[] = sprintf("%s: %s", $header, $value); - } - - return $headers; - } - - /** - * @param array $headers - * - * @return self - */ - public function setHeaders($headers) - { - $this->headers = $headers; - - return $this; - } - - /** - * @return int - */ - public function getClientTimeout() - { - return $this->clientTimeout; - } - - /** - * @param int $clientTimeout - * - * @return self - */ - public function setClientTimeout($clientTimeout) - { - $this->clientTimeout = $clientTimeout; - - return $this; - } -} diff --git a/lib/RetailCrm/Methods/V3/Customers.php b/lib/RetailCrm/Methods/V3/Customers.php deleted file mode 100644 index 2e7a7a6..0000000 --- a/lib/RetailCrm/Methods/V3/Customers.php +++ /dev/null @@ -1,206 +0,0 @@ -client->makeRequest( - '/customers', - "GET", - $parameters - ); - } - - /** - * Create a customer - * - * @param array $customer customer data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCreate(array $customer, $site = null) - { - if (! count($customer)) { - throw new \InvalidArgumentException( - 'Parameter `customer` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/customers/create', - "POST", - $this->fillSite($site, ['customer' => json_encode($customer)]) - ); - } - - /** - * Save customer IDs' (id and externalId) association in the CRM - * - * @param array $ids ids mapping - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersFixExternalIds(array $ids) - { - if (! count($ids)) { - throw new \InvalidArgumentException( - 'Method parameter must contains at least one IDs pair' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/customers/fix-external-ids', - "POST", - ['customers' => json_encode($ids)] - ); - } - - /** - * Upload array of the customers - * - * @param array $customers array of customers - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersUpload(array $customers, $site = null) - { - if (! count($customers)) { - throw new \InvalidArgumentException( - 'Parameter `customers` must contains array of the customers' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/customers/upload', - "POST", - $this->fillSite($site, ['customers' => json_encode($customers)]) - ); - } - - /** - * Get customer by id or externalId - * - * @param string $id customer identificator - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersGet($id, $by = 'externalId', $site = null) - { - $this->checkIdParameter($by); - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/customers/$id", - "GET", - $this->fillSite($site, ['by' => $by]) - ); - } - - /** - * Edit a customer - * - * @param array $customer customer data - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersEdit(array $customer, $by = 'externalId', $site = null) - { - if (!count($customer)) { - throw new \InvalidArgumentException( - 'Parameter `customer` must contains a data' - ); - } - - $this->checkIdParameter($by); - - if (!array_key_exists($by, $customer)) { - throw new \InvalidArgumentException( - sprintf('Customer array must contain the "%s" parameter.', $by) - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/customers/%s/edit', $customer[$by]), - "POST", - $this->fillSite( - $site, - ['customer' => json_encode($customer), 'by' => $by] - ) - ); - } -} diff --git a/lib/RetailCrm/Methods/V3/Orders.php b/lib/RetailCrm/Methods/V3/Orders.php deleted file mode 100644 index f38a6f0..0000000 --- a/lib/RetailCrm/Methods/V3/Orders.php +++ /dev/null @@ -1,269 +0,0 @@ -client->makeRequest( - '/orders', - "GET", - $parameters - ); - } - - /** - * Create an order - * - * @param array $order order data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersCreate(array $order, $site = null) - { - if (!count($order)) { - throw new \InvalidArgumentException( - 'Parameter `order` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/orders/create', - "POST", - $this->fillSite($site, ['order' => json_encode($order)]) - ); - } - - /** - * Save order IDs' (id and externalId) association into CRM - * - * @param array $ids order identificators - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersFixExternalIds(array $ids) - { - if (! count($ids)) { - throw new \InvalidArgumentException( - 'Method parameter must contains at least one IDs pair' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/orders/fix-external-ids', - "POST", - ['orders' => json_encode($ids) - ] - ); - } - - /** - * Returns statuses of the orders - * - * @param array $ids (default: array()) - * @param array $externalIds (default: array()) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersStatuses(array $ids = [], array $externalIds = []) - { - $parameters = []; - - if (count($ids)) { - $parameters['ids'] = $ids; - } - if (count($externalIds)) { - $parameters['externalIds'] = $externalIds; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/orders/statuses', - "GET", - $parameters - ); - } - - /** - * Upload array of the orders - * - * @param array $orders array of orders - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersUpload(array $orders, $site = null) - { - if (!count($orders)) { - throw new \InvalidArgumentException( - 'Parameter `orders` must contains array of the orders' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/orders/upload', - "POST", - $this->fillSite($site, ['orders' => json_encode($orders)]) - ); - } - - /** - * Get order by id or externalId - * - * @param string $id order identificator - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersGet($id, $by = 'externalId', $site = null) - { - $this->checkIdParameter($by); - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/orders/$id", - "GET", - $this->fillSite($site, ['by' => $by]) - ); - } - - /** - * Edit an order - * - * @param array $order order data - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersEdit(array $order, $by = 'externalId', $site = null) - { - if (!count($order)) { - throw new \InvalidArgumentException( - 'Parameter `order` must contains a data' - ); - } - - $this->checkIdParameter($by); - - if (!array_key_exists($by, $order)) { - throw new \InvalidArgumentException( - sprintf('Order array must contain the "%s" parameter.', $by) - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/orders/%s/edit', $order[$by]), - "POST", - $this->fillSite( - $site, - ['order' => json_encode($order), 'by' => $by] - ) - ); - } - - /** - * Get orders history - * @param array $filter - * @param null $page - * @param null $limit - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersHistory(array $filter = [], $page = null, $limit = null) - { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/orders/history', - "GET", - $parameters - ); - } -} diff --git a/lib/RetailCrm/Methods/V3/Packs.php b/lib/RetailCrm/Methods/V3/Packs.php deleted file mode 100644 index 3163ddf..0000000 --- a/lib/RetailCrm/Methods/V3/Packs.php +++ /dev/null @@ -1,197 +0,0 @@ -client->makeRequest( - '/orders/packs', - "GET", - $parameters - ); - } - - /** - * Create orders assembly - * - * @param array $pack pack data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersPacksCreate(array $pack, $site = null) - { - if (!count($pack)) { - throw new \InvalidArgumentException( - 'Parameter `pack` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/orders/packs/create', - "POST", - $this->fillSite($site, ['pack' => json_encode($pack)]) - ); - } - - /** - * Get orders assembly history - * - * @param array $filter (default: array()) - * @param int $page (default: null) - * @param int $limit (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersPacksHistory(array $filter = [], $page = null, $limit = null) - { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/orders/packs/history', - "GET", - $parameters - ); - } - - /** - * Get orders assembly by id - * - * @param string $id pack identificator - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersPacksGet($id) - { - if (empty($id)) { - throw new \InvalidArgumentException('Parameter `id` must be set'); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/orders/packs/$id", - "GET" - ); - } - - /** - * Delete orders assembly by id - * - * @param string $id pack identificator - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersPacksDelete($id) - { - if (empty($id)) { - throw new \InvalidArgumentException('Parameter `id` must be set'); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/orders/packs/%s/delete', $id), - "POST" - ); - } - - /** - * Edit orders assembly - * - * @param array $pack pack data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersPacksEdit(array $pack, $site = null) - { - if (!count($pack) || empty($pack['id'])) { - throw new \InvalidArgumentException( - 'Parameter `pack` must contains a data & pack `id` must be set' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/orders/packs/%s/edit', $pack['id']), - "POST", - $this->fillSite($site, ['pack' => json_encode($pack)]) - ); - } -} diff --git a/lib/RetailCrm/Methods/V3/References.php b/lib/RetailCrm/Methods/V3/References.php deleted file mode 100644 index 564eca4..0000000 --- a/lib/RetailCrm/Methods/V3/References.php +++ /dev/null @@ -1,515 +0,0 @@ -client->makeRequest( - '/reference/countries', - "GET" - ); - } - - /** - * Returns deliveryServices list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function deliveryServicesList() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/delivery-services', - "GET" - ); - } - - /** - * Edit deliveryService - * - * @param array $data delivery service data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function deliveryServicesEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/delivery-services/%s/edit', $data['code']), - "POST", - ['deliveryService' => json_encode($data)] - ); - } - - /** - * Returns deliveryTypes list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function deliveryTypesList() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/delivery-types', - "GET" - ); - } - - /** - * Edit deliveryType - * - * @param array $data delivery type data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function deliveryTypesEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/delivery-types/%s/edit', $data['code']), - "POST", - ['deliveryType' => json_encode($data)] - ); - } - - /** - * Returns orderMethods list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function orderMethodsList() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/order-methods', - "GET" - ); - } - - /** - * Edit orderMethod - * - * @param array $data order method data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function orderMethodsEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/order-methods/%s/edit', $data['code']), - "POST", - ['orderMethod' => json_encode($data)] - ); - } - - /** - * Returns orderTypes list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function orderTypesList() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/order-types', - "GET" - ); - } - - /** - * Edit orderType - * - * @param array $data order type data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function orderTypesEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/order-types/%s/edit', $data['code']), - "POST", - ['orderType' => json_encode($data)] - ); - } - - /** - * Returns paymentStatuses list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function paymentStatusesList() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/payment-statuses', - "GET" - ); - } - - /** - * Edit paymentStatus - * - * @param array $data payment status data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function paymentStatusesEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/payment-statuses/%s/edit', $data['code']), - "POST", - ['paymentStatus' => json_encode($data)] - ); - } - - /** - * Returns paymentTypes list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function paymentTypesList() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/payment-types', - "GET" - ); - } - - /** - * Edit paymentType - * - * @param array $data payment type data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function paymentTypesEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/payment-types/%s/edit', $data['code']), - "POST", - ['paymentType' => json_encode($data)] - ); - } - - /** - * Returns productStatuses list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function productStatusesList() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/product-statuses', - "GET" - ); - } - - /** - * Edit productStatus - * - * @param array $data product status data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function productStatusesEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/product-statuses/%s/edit', $data['code']), - "POST", - ['productStatus' => json_encode($data)] - ); - } - - /** - * Returns sites list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function sitesList() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/sites', - "GET" - ); - } - - /** - * Edit site - * - * @param array $data site data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function sitesEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/sites/%s/edit', $data['code']), - "POST", - ['site' => json_encode($data)] - ); - } - - /** - * Returns statusGroups list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function statusGroupsList() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/status-groups', - "GET" - ); - } - - /** - * Returns statuses list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function statusesList() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/statuses', - "GET" - ); - } - - /** - * Edit order status - * - * @param array $data status data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function statusesEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/statuses/%s/edit', $data['code']), - "POST", - ['status' => json_encode($data)] - ); - } - - /** - * Returns stores list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function storesList() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/stores', - "GET" - ); - } - - /** - * Edit store - * - * @param array $data site data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function storesEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - if (!array_key_exists('name', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "name" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/stores/%s/edit', $data['code']), - "POST", - ['store' => json_encode($data)] - ); - } -} diff --git a/lib/RetailCrm/Methods/V3/Statistic.php b/lib/RetailCrm/Methods/V3/Statistic.php deleted file mode 100644 index 5e1e976..0000000 --- a/lib/RetailCrm/Methods/V3/Statistic.php +++ /dev/null @@ -1,41 +0,0 @@ -client->makeRequest( - '/statistic/update', - "GET" - ); - } -} diff --git a/lib/RetailCrm/Methods/V3/Stores.php b/lib/RetailCrm/Methods/V3/Stores.php deleted file mode 100644 index 117ea3c..0000000 --- a/lib/RetailCrm/Methods/V3/Stores.php +++ /dev/null @@ -1,86 +0,0 @@ -client->makeRequest( - '/store/inventories', - "GET", - $parameters - ); - } - - /** - * Upload store inventories - * - * @param array $offers offers data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function storeInventoriesUpload(array $offers, $site = null) - { - if (!count($offers)) { - throw new \InvalidArgumentException( - 'Parameter `offers` must contains array of the offers' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/store/inventories/upload', - "POST", - $this->fillSite($site, ['offers' => json_encode($offers)]) - ); - } -} diff --git a/lib/RetailCrm/Methods/V3/Telephony.php b/lib/RetailCrm/Methods/V3/Telephony.php deleted file mode 100644 index 18eb068..0000000 --- a/lib/RetailCrm/Methods/V3/Telephony.php +++ /dev/null @@ -1,168 +0,0 @@ -client->makeRequest( - "/telephony/setting/$code", - "GET" - ); - } - - /** - * Call event - * - * @param string $phone phone number - * @param string $type call type - * @param array $codes - * @param array $userIds - * @param string $callExternalId - * @param string $hangupStatus - * @param string $externalPhone - * @param array $webAnalyticsData - * @param string $site (default: null) - * - * @return \RetailCrm\Response\ApiResponse - * @internal param string $code additional phone code - * @internal param string $status call status - */ - public function telephonyCallEvent( - $phone, - $type, - $codes, - $userIds = [], - $hangupStatus = null, - $externalPhone = null, - $callExternalId = null, - $webAnalyticsData = [], - $site = null - ) { - if (!isset($phone)) { - throw new \InvalidArgumentException('Phone number must be set'); - } - - if (!isset($type)) { - throw new \InvalidArgumentException('Type must be set (in|out|hangup)'); - } - - if (empty($codes)) { - throw new \InvalidArgumentException('Codes array must be set'); - } - - $parameters['phone'] = $phone; - $parameters['type'] = $type; - $parameters['codes'] = $codes; - - if (!empty($userIds)) { - $parameters['userIds'] = $userIds; - } - - $parameters['hangupStatus'] = $hangupStatus; - $parameters['callExternalId'] = $callExternalId; - $parameters['externalPhone'] = $externalPhone; - $parameters['webAnalyticsData'] = $webAnalyticsData; - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/telephony/call/event', - "POST", - ['event' => json_encode($this->fillSite($site, $parameters))] - ); - } - - /** - * Upload calls - * - * @param array $calls calls data - * - * @param bool $autoFillSite fill site code from API client in provided calls - * - * @return \RetailCrm\Response\ApiResponse - */ - public function telephonyCallsUpload(array $calls, $autoFillSite = false) - { - if (!count($calls)) { - throw new \InvalidArgumentException( - 'Parameter `calls` must contains array of the calls' - ); - } - - if ($autoFillSite) { - foreach ($calls as $key => $call) { - $calls[$key] = $this->fillSite(null, $call); - } - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/telephony/calls/upload', - "POST", - ['calls' => json_encode($calls)] - ); - } - - /** - * Get call manager - * - * @param string $phone phone number - * @param bool $details detailed information - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function telephonyCallManager($phone, $details) - { - if (!isset($phone)) { - throw new \InvalidArgumentException('Phone number must be set'); - } - - $parameters['phone'] = $phone; - $parameters['details'] = isset($details) ? $details : 0; - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/telephony/manager', - "GET", - $parameters - ); - } -} diff --git a/lib/RetailCrm/Methods/V4/Customers.php b/lib/RetailCrm/Methods/V4/Customers.php deleted file mode 100644 index fe3c687..0000000 --- a/lib/RetailCrm/Methods/V4/Customers.php +++ /dev/null @@ -1,57 +0,0 @@ -client->makeRequest( - '/customers/history', - "GET", - $parameters - ); - } -} diff --git a/lib/RetailCrm/Methods/V4/Delivery.php b/lib/RetailCrm/Methods/V4/Delivery.php deleted file mode 100644 index de002ec..0000000 --- a/lib/RetailCrm/Methods/V4/Delivery.php +++ /dev/null @@ -1,79 +0,0 @@ -client->makeRequest( - "/delivery/generic/setting/$code", - "GET" - ); - } - - /** - * Delivery tracking update - * - * @param string $code - * @param array $statusUpdate - * - * @throws \RetailCrm\Exception\InvalidJsonException - * @throws \RetailCrm\Exception\CurlException - * @throws \InvalidArgumentException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function deliveryTracking($code, array $statusUpdate) - { - if (empty($code)) { - throw new \InvalidArgumentException('Parameter `code` must be set'); - } - - if (!count($statusUpdate)) { - throw new \InvalidArgumentException( - 'Parameter `statusUpdate` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/delivery/generic/%s/tracking', $code), - "POST", - ['statusUpdate' => json_encode($statusUpdate)] - ); - } -} diff --git a/lib/RetailCrm/Methods/V4/Marketplace.php b/lib/RetailCrm/Methods/V4/Marketplace.php deleted file mode 100644 index 7a3cc99..0000000 --- a/lib/RetailCrm/Methods/V4/Marketplace.php +++ /dev/null @@ -1,51 +0,0 @@ -client->makeRequest( - sprintf('/marketplace/external/setting/%s/edit', $configuration['code']), - "POST", - ['configuration' => json_encode($configuration)] - ); - } -} diff --git a/lib/RetailCrm/Methods/V4/Orders.php b/lib/RetailCrm/Methods/V4/Orders.php deleted file mode 100644 index 32d980f..0000000 --- a/lib/RetailCrm/Methods/V4/Orders.php +++ /dev/null @@ -1,279 +0,0 @@ -client->makeRequest( - '/orders', - "GET", - $parameters - ); - } - - /** - * Create an order - * - * @param array $order order data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersCreate(array $order, $site = null) - { - if (!count($order)) { - throw new \InvalidArgumentException( - 'Parameter `order` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/orders/create', - "POST", - $this->fillSite($site, ['order' => json_encode($order)]) - ); - } - - /** - * Save order IDs' (id and externalId) association into CRM - * - * @param array $ids order identificators - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersFixExternalIds(array $ids) - { - if (! count($ids)) { - throw new \InvalidArgumentException( - 'Method parameter must contains at least one IDs pair' - ); - } - - /** @noinspection PhpUndefinedMethodInspection */ - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/orders/fix-external-ids', - "POST", - ['orders' => json_encode($ids) - ] - ); - } - - /** - * Returns statuses of the orders - * - * @param array $ids (default: array()) - * @param array $externalIds (default: array()) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersStatuses(array $ids = [], array $externalIds = []) - { - $parameters = []; - - if (count($ids)) { - $parameters['ids'] = $ids; - } - if (count($externalIds)) { - $parameters['externalIds'] = $externalIds; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/orders/statuses', - "GET", - $parameters - ); - } - - /** - * Upload array of the orders - * - * @param array $orders array of orders - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersUpload(array $orders, $site = null) - { - if (!count($orders)) { - throw new \InvalidArgumentException( - 'Parameter `orders` must contains array of the orders' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/orders/upload', - "POST", - $this->fillSite($site, ['orders' => json_encode($orders)]) - ); - } - - /** - * Get order by id or externalId - * - * @param string $id order identificator - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersGet($id, $by = 'externalId', $site = null) - { - $this->checkIdParameter($by); - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/orders/$id", - "GET", - $this->fillSite($site, ['by' => $by]) - ); - } - - /** - * Edit an order - * - * @param array $order order data - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersEdit(array $order, $by = 'externalId', $site = null) - { - if (!count($order)) { - throw new \InvalidArgumentException( - 'Parameter `order` must contains a data' - ); - } - - $this->checkIdParameter($by); - - if (!array_key_exists($by, $order)) { - throw new \InvalidArgumentException( - sprintf('Order array must contain the "%s" parameter.', $by) - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/orders/%s/edit', $order[$by]), - "POST", - $this->fillSite( - $site, - ['order' => json_encode($order), 'by' => $by] - ) - ); - } - - /** - * Get orders history - * - * @param array $filter (default: array()) - * @param int $page (default: null) - * @param int $limit (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersHistory(array $filter = [], $page = null, $limit = null) - { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/orders/history', - "GET", - $parameters - ); - } -} diff --git a/lib/RetailCrm/Methods/V4/Packs.php b/lib/RetailCrm/Methods/V4/Packs.php deleted file mode 100644 index 6582b56..0000000 --- a/lib/RetailCrm/Methods/V4/Packs.php +++ /dev/null @@ -1,27 +0,0 @@ -client->makeRequest( - '/reference/price-types', - "GET" - ); - } - - /** - * Edit price type - * - * @param array $data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function pricesTypesEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - if (!array_key_exists('name', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "name" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/price-types/%s/edit', $data['code']), - "POST", - ['priceType' => json_encode($data)] - ); - } -} diff --git a/lib/RetailCrm/Methods/V4/Settings.php b/lib/RetailCrm/Methods/V4/Settings.php deleted file mode 100644 index 5f53d01..0000000 --- a/lib/RetailCrm/Methods/V4/Settings.php +++ /dev/null @@ -1,183 +0,0 @@ -client->makeRequest( - sprintf('/store/setting/%s/edit', $configuration['code']), - "POST", - ['configuration' => json_encode($configuration)] - ); - } - - /** - * Edit telephony settings - * - * @param string $code symbolic code - * @param string $clientId client id - * @param boolean $active telephony activity - * @param mixed $name service name - * @param mixed $makeCallUrl service init url - * @param mixed $image service logo url(svg file) - * - * @param array $additionalCodes - * @param array $externalPhones - * @param bool $allowEdit - * @param bool $inputEventSupported - * @param bool $outputEventSupported - * @param bool $hangupEventSupported - * @param bool $changeUserStatusUrl - * - * @return \RetailCrm\Response\ApiResponse - */ - public function telephonySettingsEdit( - $code, - $clientId, - $active = false, - $name = false, - $makeCallUrl = false, - $image = false, - $additionalCodes = [], - $externalPhones = [], - $allowEdit = false, - $inputEventSupported = false, - $outputEventSupported = false, - $hangupEventSupported = false, - $changeUserStatusUrl = false - ) { - if (!isset($code)) { - throw new \InvalidArgumentException('Code must be set'); - } - - $parameters['code'] = $code; - - if (!isset($clientId)) { - throw new \InvalidArgumentException('client id must be set'); - } - - $parameters['clientId'] = $clientId; - - if (!isset($active)) { - $parameters['active'] = false; - } else { - $parameters['active'] = $active; - } - - if (!isset($name)) { - throw new \InvalidArgumentException('name must be set'); - } - - if (isset($name)) { - $parameters['name'] = $name; - } - - if (isset($makeCallUrl)) { - $parameters['makeCallUrl'] = $makeCallUrl; - } - - if (isset($image)) { - $parameters['image'] = $image; - } - - if (isset($additionalCodes)) { - $parameters['additionalCodes'] = $additionalCodes; - } - - if (isset($externalPhones)) { - $parameters['externalPhones'] = $externalPhones; - } - - if (isset($allowEdit)) { - $parameters['allowEdit'] = $allowEdit; - } - - if (isset($inputEventSupported)) { - $parameters['inputEventSupported'] = $inputEventSupported; - } - - if (isset($outputEventSupported)) { - $parameters['outputEventSupported'] = $outputEventSupported; - } - - if (isset($hangupEventSupported)) { - $parameters['hangupEventSupported'] = $hangupEventSupported; - } - - if (isset($changeUserStatusUrl)) { - $parameters['changeUserStatusUrl'] = $changeUserStatusUrl; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/telephony/setting/$code/edit", - "POST", - ['configuration' => json_encode($parameters)] - ); - } - - /** - * Edit delivery configuration - * - * @param array $configuration - * - * @throws \RetailCrm\Exception\InvalidJsonException - * @throws \RetailCrm\Exception\CurlException - * @throws \InvalidArgumentException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function deliverySettingsEdit(array $configuration) - { - if (!count($configuration) || empty($configuration['code'])) { - throw new \InvalidArgumentException( - 'Parameter `configuration` must contains a data & configuration `code` must be set' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/delivery/generic/setting/%s/edit', $configuration['code']), - "POST", - ['configuration' => json_encode($configuration)] - ); - } -} diff --git a/lib/RetailCrm/Methods/V4/Statistic.php b/lib/RetailCrm/Methods/V4/Statistic.php deleted file mode 100644 index 286b0b1..0000000 --- a/lib/RetailCrm/Methods/V4/Statistic.php +++ /dev/null @@ -1,27 +0,0 @@ -client->makeRequest( - "/store/setting/$code", - "GET" - ); - } - - - /** - * Upload store prices - * - * @param array $prices prices data - * @param string $site default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function storePricesUpload(array $prices, $site = null) - { - if (!count($prices)) { - throw new \InvalidArgumentException( - 'Parameter `prices` must contains array of the prices' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/store/prices/upload', - "POST", - $this->fillSite($site, ['prices' => json_encode($prices)]) - ); - } - - /** - * Get products - * - * @param array $filter (default: array()) - * @param int $page (default: null) - * @param int $limit (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function storeProducts(array $filter = [], $page = null, $limit = null) - { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/store/products', - "GET", - $parameters - ); - } -} diff --git a/lib/RetailCrm/Methods/V4/Telephony.php b/lib/RetailCrm/Methods/V4/Telephony.php deleted file mode 100644 index 5a0716d..0000000 --- a/lib/RetailCrm/Methods/V4/Telephony.php +++ /dev/null @@ -1,27 +0,0 @@ -client->makeRequest( - '/users', - "GET", - $parameters - ); - } - - /** - * Get user groups - * - * @param null $page - * @param null $limit - * - * @throws \RetailCrm\Exception\InvalidJsonException - * @throws \RetailCrm\Exception\CurlException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function usersGroups($page = null, $limit = null) - { - $parameters = []; - - if (null !== $page) { - $parameters['page'] = (int)$page; - } - if (null !== $limit) { - $parameters['limit'] = (int)$limit; - } - - /** @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/user-groups', - "GET", - $parameters - ); - } - - /** - * Returns user data - * - * @param integer $id user ID - * - * @throws \RetailCrm\Exception\InvalidJsonException - * @throws \RetailCrm\Exception\CurlException - * @throws \InvalidArgumentException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function usersGet($id) - { - /** @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest("/users/$id", "GET"); - } -} diff --git a/lib/RetailCrm/Methods/V5/Costs.php b/lib/RetailCrm/Methods/V5/Costs.php deleted file mode 100644 index d1278dd..0000000 --- a/lib/RetailCrm/Methods/V5/Costs.php +++ /dev/null @@ -1,216 +0,0 @@ -client->makeRequest( - '/costs', - "GET", - $parameters - ); - } - - /** - * Create a cost - * - * @param array $cost cost data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function costsCreate(array $cost, $site = null) - { - if (!count($cost)) { - throw new \InvalidArgumentException( - 'Parameter `cost` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/costs/create', - "POST", - $this->fillSite($site, ['cost' => json_encode($cost)]) - ); - } - - /** - * Delete costs set - * - * @param array $ids costs identifiers - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function costsDelete(array $ids) - { - if (!count($ids)) { - throw new \InvalidArgumentException( - 'Parameter `ids` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/costs/delete', - "POST", - ['ids' => json_encode($ids)] - ); - } - - /** - * Upload costs - * - * @param array $costs costs array - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function costsUpload($costs) - { - if (!count($costs)) { - throw new \InvalidArgumentException( - 'Parameter `costs` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/costs/upload', - "POST", - ['costs' => json_encode($costs)] - ); - } - - /** - * Get cost by id - * - * @param string $id customer identificator - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function costsGet($id) - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/costs/$id", - "GET" - ); - } - - /** - * Edit a cost - * - * @param array $cost cost data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function costsEdit(array $cost, $site = null) - { - if (!count($cost)) { - throw new \InvalidArgumentException( - 'Parameter `cost` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/costs/%s/edit', $cost['id']), - "POST", - $this->fillSite( - $site, - ['cost' => json_encode($cost)] - ) - ); - } - - /** - * Delete cost by id - * - * @param string $id cost identifier - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function costsDeleteById($id) - { - if (!empty($id)) { - throw new \InvalidArgumentException( - 'Parameter `id` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/costs/%s/delete', $id), - "POST" - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/CustomFields.php b/lib/RetailCrm/Methods/V5/CustomFields.php deleted file mode 100644 index d940ca4..0000000 --- a/lib/RetailCrm/Methods/V5/CustomFields.php +++ /dev/null @@ -1,261 +0,0 @@ -client->makeRequest( - '/custom-fields', - "GET", - $parameters - ); - } - - /** - * Create custom field - * - * @param $entity - * @param $customField - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customFieldsCreate($entity, $customField) - { - if (!count($customField) || - empty($customField['code']) || - empty($customField['name']) || - empty($customField['type']) - ) { - throw new \InvalidArgumentException( - 'Parameter `customField` must contain a data & fields `code`, `name` & `type` must be set' - ); - } - - if (empty($entity) || !in_array($entity, ['customer', 'order', 'customer_corporate', 'company'])) { - throw new \InvalidArgumentException( - sprintf( - 'Parameter `entity` must contain a data & value must be %s', - '`order`, `customer`, `customer_corporate` or `company`' - ) - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/custom-fields/$entity/create", - "POST", - ['customField' => json_encode($customField)] - ); - } - - /** - * Edit custom field - * - * @param $entity - * @param $customField - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customFieldsEdit($entity, $customField) - { - if (!count($customField) || empty($customField['code'])) { - throw new \InvalidArgumentException( - 'Parameter `customField` must contain a data & fields `code` must be set' - ); - } - - if (empty($entity) || !in_array($entity, ['customer', 'order', 'customer_corporate', 'company'])) { - throw new \InvalidArgumentException( - sprintf( - 'Parameter `entity` must contain a data & value must be %s', - '`order`, `customer`, `customer_corporate` or `company`' - ) - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/custom-fields/$entity/{$customField['code']}/edit", - "POST", - ['customField' => json_encode($customField)] - ); - } - - /** - * Get custom field - * - * @param $entity - * @param $code - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customFieldsGet($entity, $code) - { - if (empty($code)) { - throw new \InvalidArgumentException( - 'Parameter `code` must be not empty' - ); - } - - if (empty($entity) || !in_array($entity, ['customer', 'order', 'customer_corporate', 'company'])) { - throw new \InvalidArgumentException( - sprintf( - 'Parameter `entity` must contain a data & value must be %s', - '`order`, `customer`, `customer_corporate` or `company`' - ) - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/custom-fields/$entity/$code", - "GET" - ); - } - - /** - * Get custom dictionaries list - * - * @param array $filter - * @param null $limit - * @param null $page - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customDictionariesList(array $filter = [], $limit = null, $page = null) - { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/custom-fields/dictionaries', - "GET", - $parameters - ); - } - - /** - * Create custom dictionary - * - * @param $customDictionary - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customDictionariesCreate($customDictionary) - { - if (!count($customDictionary) || - empty($customDictionary['code']) || - empty($customDictionary['elements']) - ) { - throw new \InvalidArgumentException( - 'Parameter `dictionary` must contain a data & fields `code` & `elemets` must be set' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/custom-fields/dictionaries/create", - "POST", - ['customDictionary' => json_encode($customDictionary)] - ); - } - - /** - * Edit custom dictionary - * - * @param $customDictionary - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customDictionariesEdit($customDictionary) - { - if (!count($customDictionary) || - empty($customDictionary['code']) || - empty($customDictionary['elements']) - ) { - throw new \InvalidArgumentException( - 'Parameter `dictionary` must contain a data & fields `code` & `elemets` must be set' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/custom-fields/dictionaries/{$customDictionary['code']}/edit", - "POST", - ['customDictionary' => json_encode($customDictionary)] - ); - } - - /** - * Get custom dictionary - * - * @param $code - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customDictionariesGet($code) - { - if (empty($code)) { - throw new \InvalidArgumentException( - 'Parameter `code` must be not empty' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/custom-fields/dictionaries/$code", - "GET" - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/Customers.php b/lib/RetailCrm/Methods/V5/Customers.php deleted file mode 100644 index daeb6d9..0000000 --- a/lib/RetailCrm/Methods/V5/Customers.php +++ /dev/null @@ -1,144 +0,0 @@ -client->makeRequest( - '/customers/combine', - "POST", - [ - 'customers' => json_encode($customers), - 'resultCustomer' => json_encode($resultCustomer) - ] - ); - } - - /** - * Returns filtered customers notes list - * - * @param array $filter (default: array()) - * @param int $page (default: null) - * @param int $limit (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersNotesList(array $filter = [], $page = null, $limit = null) - { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/customers/notes', - "GET", - $parameters - ); - } - - /** - * Create customer note - * - * @param array $note (default: array()) - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersNotesCreate($note, $site = null) - { - if (empty($note['customer']['id']) && empty($note['customer']['externalId'])) { - throw new \InvalidArgumentException( - 'Customer identifier must be set' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/customers/notes/create', - "POST", - $this->fillSite($site, ['note' => json_encode($note)]) - ); - } - - /** - * Delete customer note - * - * @param integer $id - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersNotesDelete($id) - { - if (empty($id)) { - throw new \InvalidArgumentException( - 'Note id must be set' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/customers/notes/$id/delete", - "POST" - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/CustomersCorporate.php b/lib/RetailCrm/Methods/V5/CustomersCorporate.php deleted file mode 100644 index 108caaa..0000000 --- a/lib/RetailCrm/Methods/V5/CustomersCorporate.php +++ /dev/null @@ -1,656 +0,0 @@ -client->makeRequest( - '/customers-corporate', - "GET", - $parameters - ); - } - - /** - * Create a corporate customer - * - * @param array $customerCorporate corporate customer data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateCreate(array $customerCorporate, $site = null) - { - if (! count($customerCorporate)) { - throw new \InvalidArgumentException( - 'Parameter `customerCorporate` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/customers-corporate/create', - "POST", - $this->fillSite($site, ['customerCorporate' => json_encode($customerCorporate)]) - ); - } - - /** - * Save corporate customer IDs' (id and externalId) association in the CRM - * - * @param array $ids ids mapping - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateFixExternalIds(array $ids) - { - if (! count($ids)) { - throw new \InvalidArgumentException( - 'Method parameter must contains at least one IDs pair' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/customers-corporate/fix-external-ids', - "POST", - ['customersCorporate' => json_encode($ids)] - ); - } - - - /** - * Get corporate customers history - * @param array $filter - * @param null $page - * @param null $limit - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateHistory(array $filter = [], $page = null, $limit = null) - { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/customers-corporate/history', - "GET", - $parameters - ); - } - - /** - * Returns filtered corporate customers notes list - * - * @param array $filter (default: array()) - * @param int $page (default: null) - * @param int $limit (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateNotesList(array $filter = [], $page = null, $limit = null) - { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/customers-corporate/notes', - "GET", - $parameters - ); - } - - /** - * Create corporate customer note - * - * @param array $note (default: array()) - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateNotesCreate($note, $site = null) - { - if (empty($note['customer']['id']) && empty($note['customer']['externalId'])) { - throw new \InvalidArgumentException( - 'Customer identifier must be set' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/customers-corporate/notes/create', - "POST", - $this->fillSite($site, ['note' => json_encode($note)]) - ); - } - - /** - * Delete corporate customer note - * - * @param integer $id - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateNotesDelete($id) - { - if (empty($id)) { - throw new \InvalidArgumentException( - 'Note id must be set' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/customers-corporate/notes/$id/delete", - "POST" - ); - } - - /** - * Upload array of the corporate customers - * - * @param array $customersCorporate array of corporate customers - * @param string $site (default: null) - * - * @return \RetailCrm\Response\ApiResponse - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @throws \InvalidArgumentException - */ - public function customersCorporateUpload(array $customersCorporate, $site = null) - { - if (!count($customersCorporate)) { - throw new \InvalidArgumentException( - 'Parameter `customersCorporate` must contains array of the corporate customers' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/customers-corporate/upload', - "POST", - $this->fillSite($site, ['customersCorporate' => json_encode($customersCorporate)]) - ); - } - - /** - * Get corporate customer by id or externalId - * - * @param string $id corporate customer identifier - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateGet($id, $by = 'externalId', $site = null) - { - $this->checkIdParameter($by); - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/customers-corporate/$id", - "GET", - $this->fillSite($site, ['by' => $by]) - ); - } - - /** - * Get corporate customer addresses by id or externalId - * - * @param string $id corporate customer identifier - * @param array $filter (default: array()) - * @param int $page (default: null) - * @param int $limit (default: null) - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateAddresses( - $id, - array $filter = [], - $page = null, - $limit = null, - $by = 'externalId', - $site = null - ) { - $this->checkIdParameter($by); - - $parameters = ['by' => $by]; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/customers-corporate/$id/addresses", - "GET", - $this->fillSite($site, $parameters) - ); - } - - /** - * Create corporate customer address - * - * @param string $id corporate customer identifier - * @param array $address (default: array()) - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateAddressesCreate($id, array $address = [], $by = 'externalId', $site = null) - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/customers-corporate/$id/addresses/create", - "POST", - $this->fillSite($site, ['address' => json_encode($address), 'by' => $by]) - ); - } - - /** - * Edit corporate customer address - * - * @param string $customerId corporate customer identifier - * @param string $addressId corporate customer identifier - * @param array $address (default: array()) - * @param string $customerBy (default: 'externalId') - * @param string $addressBy (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateAddressesEdit( - $customerId, - $addressId, - array $address = [], - $customerBy = 'externalId', - $addressBy = 'externalId', - $site = null - ) { - $addressFiltered = array_filter($address); - - if ((count(array_keys($addressFiltered)) <= 1) - && (!isset($addressFiltered['text']) - || (isset($addressFiltered['text']) && empty($addressFiltered['text'])) - ) - ) { - throw new \InvalidArgumentException( - 'Parameter `address` must contain address text or all other address field' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/customers-corporate/$customerId/addresses/$addressId/edit", - "POST", - $this->fillSite($site, [ - 'address' => json_encode($address), - 'by' => $customerBy, - 'entityBy' => $addressBy - ]) - ); - } - - /** - * Get corporate customer companies by id or externalId - * - * @param string $id corporate customer identifier - * @param array $filter (default: array()) - * @param int $page (default: null) - * @param int $limit (default: null) - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateCompanies( - $id, - array $filter = [], - $page = null, - $limit = null, - $by = 'externalId', - $site = null - ) { - $this->checkIdParameter($by); - - $parameters = ['by' => $by]; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/customers-corporate/$id/companies", - "GET", - $this->fillSite($site, $parameters) - ); - } - - /** - * Create corporate customer company - * - * @param string $id corporate customer identifier - * @param array $company (default: array()) - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateCompaniesCreate($id, array $company = [], $by = 'externalId', $site = null) - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/customers-corporate/$id/companies/create", - "POST", - $this->fillSite($site, ['company' => json_encode($company), 'by' => $by]) - ); - } - - /** - * Edit corporate customer company - * - * @param string $customerId corporate customer identifier - * @param string $companyId corporate customer identifier - * @param array $company (default: array()) - * @param string $customerBy (default: 'externalId') - * @param string $companyBy (default: 'externalId') - * @param string $site (default: null) - * - * @return \RetailCrm\Response\ApiResponse - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - */ - public function customersCorporateCompaniesEdit( - $customerId, - $companyId, - array $company = [], - $customerBy = 'externalId', - $companyBy = 'externalId', - $site = null - ) { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/customers-corporate/$customerId/companies/$companyId/edit", - "POST", - $this->fillSite($site, [ - 'company' => json_encode($company), - 'by' => $customerBy, - 'entityBy' => $companyBy - ]) - ); - } - - /** - * Get corporate customer contacts by id or externalId - * - * @param string $id corporate customer identifier - * @param array $filter (default: array()) - * @param int $page (default: null) - * @param int $limit (default: null) - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateContacts( - $id, - array $filter = [], - $page = null, - $limit = null, - $by = 'externalId', - $site = null - ) { - $this->checkIdParameter($by); - - $parameters = ['by' => $by]; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/customers-corporate/$id/contacts", - "GET", - $this->fillSite($site, $parameters) - ); - } - - /** - * Create corporate customer contact - * - * @param string $id corporate customer identifier - * @param array $contact (default: array()) - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @return \RetailCrm\Response\ApiResponse - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @throws \InvalidArgumentException - */ - public function customersCorporateContactsCreate($id, array $contact = [], $by = 'externalId', $site = null) - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/customers-corporate/$id/contacts/create", - "POST", - $this->fillSite($site, ['contact' => json_encode($contact), 'by' => $by]) - ); - } - - /** - * Edit corporate customer contact - * - * @param string $customerId corporate customer identifier - * @param string $contactId corporate customer identifier - * @param array $contact (default: array()) - * @param string $customerBy (default: 'externalId') - * @param string $contactBy (default: 'externalId') - * @param string $site (default: null) - * - * @return \RetailCrm\Response\ApiResponse - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - */ - public function customersCorporateContactsEdit( - $customerId, - $contactId, - array $contact = [], - $customerBy = 'externalId', - $contactBy = 'externalId', - $site = null - ) { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/customers-corporate/$customerId/contacts/$contactId/edit", - "POST", - $this->fillSite($site, [ - 'contact' => json_encode($contact), - 'by' => $customerBy, - 'entityBy' => $contactBy - ]) - ); - } - - /** - * Edit a corporate customer - * - * @param array $customerCorporate corporate customer data - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function customersCorporateEdit(array $customerCorporate, $by = 'externalId', $site = null) - { - if (!count($customerCorporate)) { - throw new \InvalidArgumentException( - 'Parameter `customerCorporate` must contains a data' - ); - } - - $this->checkIdParameter($by); - - if (!array_key_exists($by, $customerCorporate)) { - throw new \InvalidArgumentException( - sprintf('Corporate customer array must contain the "%s" parameter.', $by) - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/customers-corporate/%s/edit', $customerCorporate[$by]), - "POST", - $this->fillSite( - $site, - ['customerCorporate' => json_encode($customerCorporate), 'by' => $by] - ) - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/Delivery.php b/lib/RetailCrm/Methods/V5/Delivery.php deleted file mode 100644 index 70d058d..0000000 --- a/lib/RetailCrm/Methods/V5/Delivery.php +++ /dev/null @@ -1,178 +0,0 @@ -client->makeRequest( - '/delivery/shipments', - "GET", - $parameters - ); - } - - /** - * Create delivery shipment - * - * @param array $shipment (default: array()) - * @param string $deliveryType (default: string) - * @param null $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function deliveryShipmentsCreate( - array $shipment, - $deliveryType, - $site = null - ) { - if (!count($shipment)) { - throw new \InvalidArgumentException( - 'Parameter `shipment` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/delivery/shipments/create', - "POST", - $this->fillSite( - $site, - [ - 'deliveryShipment' => json_encode($shipment), - 'deliveryType' => $deliveryType - ] - ) - ); - } - - /** - * Get shipment - * - * @param string $id shipment id - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function deliveryShipmentGet($id) - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf("/delivery/shipments/%s", $id), - "GET" - ); - } - - /** - * Edit delivery shipment - * - * @param array $shipment (default: array()) - * @param null $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function deliveryShipmentsEdit(array $shipment, $site = null) - { - if (!count($shipment)) { - throw new \InvalidArgumentException( - 'Parameter `shipment` must contains a data' - ); - } - - if (empty($shipment['id'])) { - throw new \InvalidArgumentException( - 'Parameter `shipment` must contains an `id` field' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf("/delivery/shipments/%s/edit", $shipment['id']), - "POST", - $this->fillSite( - $site, - [ - 'deliveryShipment' => json_encode($shipment) - ] - ) - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/Files.php b/lib/RetailCrm/Methods/V5/Files.php deleted file mode 100644 index 834f8ed..0000000 --- a/lib/RetailCrm/Methods/V5/Files.php +++ /dev/null @@ -1,209 +0,0 @@ -client->makeRequest( - '/files', - "GET", - $parameters - ); - } - - /** - * Upload file - * - * @param string $file filepath - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function fileUpload($file) - { - if (!file_exists($file)) { - throw new \InvalidArgumentException("File doesn't exist"); - } - - if (filesize($file) == 0) { - throw new \InvalidArgumentException("Empty file provided"); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/files/upload', - "POST", - ["file" => $file] - ); - } - - /** - * Get file by id - * - * @param string $id file ID - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function fileGet($id) - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/files/$id", - "GET" - ); - } - - /** - * Delete file by id - * - * @param string $id file ID - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function fileDelete($id) - { - if (empty($id)) { - throw new \InvalidArgumentException( - 'Parameter `id` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/files/%s/delete', $id), - "POST" - ); - } - - /** - * Download file by id - * - * @param string $id file ID - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function fileDownload($id) - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRawRequest( - sprintf('/files/%s/download', $id), - "GET" - ); - } - - /** - * Edit file data - * - * @param int $id file ID - * @param array $file file data - * - * $file = [ - * 'filename' => 'Test file', - * 'attachment' => [ - * [ - * 'customer' => [ - * 'id' => 1 - * ], - * 'order' => [ - * 'id' => 1 - * ] - * ] - * ] - * ]; - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function fileEdit($id, array $file) - { - if (empty($id)) { - throw new \InvalidArgumentException( - 'Parameter `id` can`t be blank' - ); - } - - if (empty($file)) { - throw new \InvalidArgumentException( - 'Parameter `file` must contains a data' - ); - } - - $allowedFields = ['filename', 'attachment']; - foreach (array_keys($file) as $field) { - if (!in_array($field, $allowedFields)) { - throw new \InvalidArgumentException( - 'Invalid structure of `file` parameter' - ); - } - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/files/%s/edit', $id), - "POST", - ['file' => json_encode($file), 'id' => $id] - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/IntegrationPayments.php b/lib/RetailCrm/Methods/V5/IntegrationPayments.php deleted file mode 100644 index 05e18cb..0000000 --- a/lib/RetailCrm/Methods/V5/IntegrationPayments.php +++ /dev/null @@ -1,95 +0,0 @@ -client->makeRequest( - '/payment/create-invoice', - 'POST', - [ - 'createInvoice' => json_encode($createInvoice) - ] - ); - } - - /** - * Update Invoice - * - * @param array $updateInvoice - * @return \RetailCrm\Response\ApiResponse - */ - public function paymentUpdateInvoice($updateInvoice) - { - if (!count($updateInvoice)) { - throw new \InvalidArgumentException( - 'Parameters `updateInvoice` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/payment/update-invoice', - 'POST', - [ - 'updateInvoice' => json_encode($updateInvoice) - ] - ); - } - - /** - * Check Invoice - * - * @param array $check - * @return \RetailCrm\Response\ApiResponse - */ - public function paymentCheckInvoice($check) - { - if (!count($check)) { - throw new \InvalidArgumentException( - 'Parameters `check` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/payment/check', - 'POST', - [ - 'check' => json_encode($check) - ] - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/Module.php b/lib/RetailCrm/Methods/V5/Module.php deleted file mode 100644 index 5322d08..0000000 --- a/lib/RetailCrm/Methods/V5/Module.php +++ /dev/null @@ -1,76 +0,0 @@ -client->makeRequest( - sprintf('/integration-modules/%s', $code), - "GET" - ); - } - - /** - * Edit module configuration - * - * @param array $configuration - * - * @throws \RetailCrm\Exception\InvalidJsonException - * @throws \RetailCrm\Exception\CurlException - * @throws \InvalidArgumentException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function integrationModulesEdit(array $configuration) - { - if (!count($configuration) || empty($configuration['code'])) { - throw new \InvalidArgumentException( - 'Parameter `configuration` must contains a data & configuration `code` must be set' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/integration-modules/%s/edit', $configuration['code']), - "POST", - ['integrationModule' => json_encode($configuration)] - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/Orders.php b/lib/RetailCrm/Methods/V5/Orders.php deleted file mode 100644 index e2b44f5..0000000 --- a/lib/RetailCrm/Methods/V5/Orders.php +++ /dev/null @@ -1,153 +0,0 @@ -client->makeRequest( - '/orders/combine', - "POST", - [ - 'technique' => $technique, - 'order' => json_encode($order), - 'resultOrder' => json_encode($resultOrder) - ] - ); - } - - /** - * Create an order payment - * - * @param array $payment order data - * @param null $site site code - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersPaymentCreate(array $payment, $site = null) - { - if (!count($payment)) { - throw new \InvalidArgumentException( - 'Parameter `payment` must contains a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/orders/payments/create', - "POST", - $this->fillSite( - $site, - ['payment' => json_encode($payment)] - ) - ); - } - - /** - * Edit an order payment - * - * @param array $payment order data - * @param string $by by key - * @param null $site site code - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersPaymentEdit(array $payment, $by = 'id', $site = null) - { - if (!count($payment)) { - throw new \InvalidArgumentException( - 'Parameter `payment` must contains a data' - ); - } - - $this->checkIdParameter($by); - - if (!array_key_exists($by, $payment)) { - throw new \InvalidArgumentException( - sprintf('Order array must contain the "%s" parameter.', $by) - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/orders/payments/%s/edit', $payment[$by]), - "POST", - $this->fillSite( - $site, - ['payment' => json_encode($payment), 'by' => $by] - ) - ); - } - - /** - * Edit an order payment - * - * @param string $id payment id - * - * @return \RetailCrm\Response\ApiResponse - */ - public function ordersPaymentDelete($id) - { - if (!$id) { - throw new \InvalidArgumentException( - 'Parameter `id` must be set' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/orders/payments/%s/delete', $id), - "POST" - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/Packs.php b/lib/RetailCrm/Methods/V5/Packs.php deleted file mode 100644 index e4517f5..0000000 --- a/lib/RetailCrm/Methods/V5/Packs.php +++ /dev/null @@ -1,27 +0,0 @@ -client->makeRequest( - '/reference/cost-groups', - "GET" - ); - } - - /** - * Edit costs groups - * - * @param array $data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function costGroupsEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - if (!array_key_exists('name', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "name" parameter.' - ); - } - - if (!array_key_exists('color', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "color" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/cost-groups/%s/edit', $data['code']), - "POST", - ['costGroup' => json_encode($data)] - ); - } - - /** - * Get costs items - * - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function costItems() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/cost-items', - "GET" - ); - } - - /** - * Edit costs items - * - * @param array $data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function costItemsEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - if (!array_key_exists('name', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "name" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/cost-items/%s/edit', $data['code']), - "POST", - ['costItem' => json_encode($data)] - ); - } - - /** - * Get legal entities - * - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function legalEntities() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/legal-entities', - "GET" - ); - } - - /** - * Edit legal entity - * - * @param array $data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function legalEntitiesEdit(array $data) - { - if (!array_key_exists('code', $data)) { - throw new \InvalidArgumentException( - 'Data must contain "code" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/legal-entities/%s/edit', $data['code']), - "POST", - ['legalEntity' => json_encode($data)] - ); - } - - /** - * Get couriers - * - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function couriersList() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/couriers', - "GET" - ); - } - - /** - * Create courier - * - * @param array $courier - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function couriersCreate(array $courier) - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/couriers/create', - "POST", - ['courier' => json_encode($courier)] - ); - } - - /** - * Edit courier - * - * @param array $courier - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function couriersEdit(array $courier) - { - if (!array_key_exists('id', $courier)) { - throw new \InvalidArgumentException( - 'Data must contain "id" parameter.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/couriers/%s/edit', $courier['id']), - "POST", - ['courier' => json_encode($courier)] - ); - } - - /** - * Get units - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function unitsList() - { - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/reference/units', - "GET" - ); - } - - /** - * Edit unit - * - * @param array $unit - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function unitsEdit(array $unit) - { - if (empty($unit['code']) || empty($unit['name']) || empty($unit['sym'])) { - throw new \InvalidArgumentException( - '`code`, `name` and `sym` parameters must not be empty.' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - sprintf('/reference/units/%s/edit', $unit['code']), - "POST", - ['unit' => json_encode($unit)] - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/Segments.php b/lib/RetailCrm/Methods/V5/Segments.php deleted file mode 100644 index 7022a18..0000000 --- a/lib/RetailCrm/Methods/V5/Segments.php +++ /dev/null @@ -1,54 +0,0 @@ -client->makeRequest( - '/segments', - "GET", - $parameters - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/Settings.php b/lib/RetailCrm/Methods/V5/Settings.php deleted file mode 100644 index b41f74a..0000000 --- a/lib/RetailCrm/Methods/V5/Settings.php +++ /dev/null @@ -1,38 +0,0 @@ -client->makeRequest( - '/settings', - "GET", - [] - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/Statistic.php b/lib/RetailCrm/Methods/V5/Statistic.php deleted file mode 100644 index b53e26c..0000000 --- a/lib/RetailCrm/Methods/V5/Statistic.php +++ /dev/null @@ -1,27 +0,0 @@ -client->makeRequest( - '/store/product-groups', - "GET", - $parameters - ); - } - - /** - * Get products properties - * - * @param array $filter (default: array()) - * @param int $page (default: null) - * @param int $limit (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return \RetailCrm\Response\ApiResponse - */ - public function storeProductsProperties(array $filter = [], $page = null, $limit = null) - { - $parameters = []; - - if (count($filter)) { - $parameters['filter'] = $filter; - } - if (null !== $page) { - $parameters['page'] = (int) $page; - } - if (null !== $limit) { - $parameters['limit'] = (int) $limit; - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - '/store/products/properties', - "GET", - $parameters - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/Tasks.php b/lib/RetailCrm/Methods/V5/Tasks.php deleted file mode 100644 index 8920184..0000000 --- a/lib/RetailCrm/Methods/V5/Tasks.php +++ /dev/null @@ -1,132 +0,0 @@ -client->makeRequest( - '/tasks', - "GET", - $parameters - ); - } - - /** - * Create task - * - * @param array $task - * @param null $site - * - * @return \RetailCrm\Response\ApiResponse - * - */ - public function tasksCreate($task, $site = null) - { - if (!count($task)) { - throw new \InvalidArgumentException( - 'Parameter `task` must contain a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/tasks/create", - "POST", - $this->fillSite( - $site, - ['task' => json_encode($task)] - ) - ); - } - - /** - * Edit task - * - * @param array $task - * @param null $site - * - * @return \RetailCrm\Response\ApiResponse - * - */ - public function tasksEdit($task, $site = null) - { - if (!count($task)) { - throw new \InvalidArgumentException( - 'Parameter `task` must contain a data' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/tasks/{$task['id']}/edit", - "POST", - $this->fillSite( - $site, - ['task' => json_encode($task)] - ) - ); - } - - /** - * Get custom dictionary - * - * @param $id - * - * @return \RetailCrm\Response\ApiResponse - */ - public function tasksGet($id) - { - if (empty($id)) { - throw new \InvalidArgumentException( - 'Parameter `id` must be not empty' - ); - } - - /* @noinspection PhpUndefinedMethodInspection */ - return $this->client->makeRequest( - "/tasks/$id", - "GET" - ); - } -} diff --git a/lib/RetailCrm/Methods/V5/Telephony.php b/lib/RetailCrm/Methods/V5/Telephony.php deleted file mode 100644 index 95b9a21..0000000 --- a/lib/RetailCrm/Methods/V5/Telephony.php +++ /dev/null @@ -1,36 +0,0 @@ -client->makeRequest( - "/users/$id/status", - "POST", - ['status' => $status] - ); - } -} diff --git a/lib/RetailCrm/Response/ApiResponse.php b/lib/RetailCrm/Response/ApiResponse.php deleted file mode 100644 index a8a4af4..0000000 --- a/lib/RetailCrm/Response/ApiResponse.php +++ /dev/null @@ -1,222 +0,0 @@ -statusCode = (int) $statusCode; - $this->rawResponse = $responseBody; - } - - /** - * Deserialize JSON from raw response body - * - * @return $this - */ - public function asJsonResponse() - { - if (!empty($this->rawResponse)) { - $response = json_decode($this->rawResponse, true); - - if (!$response && JSON_ERROR_NONE !== ($error = json_last_error())) { - throw new InvalidJsonException( - "Invalid JSON in the API response body. Error code #$error", - $error - ); - } - - $this->response = $response; - } - - return $this; - } - - /** - * Return HTTP response status code - * - * @return int - */ - public function getStatusCode() - { - return $this->statusCode; - } - - /** - * Return HTTP response - * - * @return array - */ - public function getResponse() - { - return $this->response; - } - - /** - * Return HTTP raw response body - * - * @return string - */ - public function getResponseBody() - { - return $this->rawResponse; - } - - /** - * HTTP request was successful - * - * @return bool - */ - public function isSuccessful() - { - return $this->statusCode < 400; - } - - /** - * Allow to access for the property throw class method - * - * @param string $name method name - * @param mixed $arguments method parameters - * - * @throws \InvalidArgumentException - * - * @return mixed - */ - public function __call($name, $arguments) - { - // convert getSomeProperty to someProperty - $propertyName = strtolower(substr($name, 3, 1)) . substr($name, 4); - - if (!isset($this->response[$propertyName])) { - throw new \InvalidArgumentException("Method \"$name\" not found"); - } - - return $this->response[$propertyName]; - } - - /** - * Allow to access for the property throw object property - * - * @param string $name property name - * - * @throws \InvalidArgumentException - * - * @return mixed - */ - public function __get($name) - { - if (!isset($this->response[$name])) { - throw new \InvalidArgumentException("Property \"$name\" not found"); - } - - return $this->response[$name]; - } - - /** - * Allow to check if the property exists through object property - * - * @param string $name property name - * - * @return bool - */ - public function __isset($name) - { - return isset($this->response[$name]); - } - - /** - * Offset set - * - * @param mixed $offset offset - * @param mixed $value value - * - * @throws \BadMethodCallException - * @return void - */ - public function offsetSet($offset, $value) - { - throw new \BadMethodCallException('This activity not allowed'); - } - - /** - * Offset unset - * - * @param mixed $offset offset - * - * @throws \BadMethodCallException - * @return void - */ - public function offsetUnset($offset) - { - throw new \BadMethodCallException('This call not allowed'); - } - - /** - * Check offset - * - * @param mixed $offset offset - * - * @return bool - */ - public function offsetExists($offset) - { - return isset($this->response[$offset]); - } - - /** - * Get offset - * - * @param mixed $offset offset - * - * @throws \InvalidArgumentException - * - * @return mixed - */ - public function offsetGet($offset) - { - if (!isset($this->response[$offset])) { - throw new \InvalidArgumentException("Property \"$offset\" not found"); - } - - return $this->response[$offset]; - } -} - diff --git a/models/.gitkeep b/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..d12440d --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,13 @@ + + + + + + + + + + src/ + tests/ + diff --git a/phpdoc.dist.xml b/phpdoc.dist.xml new file mode 100644 index 0000000..a01140e --- /dev/null +++ b/phpdoc.dist.xml @@ -0,0 +1,21 @@ + + + RetailCRM API Client + + docs/build/html + docs/build/cache + + + + public + + src + + + + diff --git a/phpmd.xml b/phpmd.xml new file mode 100644 index 0000000..3cd0206 --- /dev/null +++ b/phpmd.xml @@ -0,0 +1,47 @@ + + + Ruleset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + tests/* + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..1494f51 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: max + paths: + - src + - tests diff --git a/phpunit.xml.dist b/phpunit.xml.dist index addf3bf..b02cb27 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,12 @@ - - - - tests - - - - - - lib - - - - - - - + + + src + dev + + + + + + + + tests/src/Command/ClearModelsCommandTest.php + tests/src/Command/GenerateModelsCommandTest.php + tests/src/Command/VerifyModelsCommandTest.php + tests/src + + + + + + + + diff --git a/src/Builder/ClientBuilder.php b/src/Builder/ClientBuilder.php new file mode 100644 index 0000000..60bb1f6 --- /dev/null +++ b/src/Builder/ClientBuilder.php @@ -0,0 +1,492 @@ +apiUrl = $apiUrl; + return $this; + } + + /** + * Request authenticator to append into request transformer pipeline. + * + * Do not use it if you already added a proper authenticator in the pipeline manually. + * You can use this method to drop authenticator from client builder (use null). + * + * @param \RetailCrm\Api\Interfaces\HandlerInterface|null $authenticator + * + * @return ClientBuilder + */ + public function setAuthenticatorHandler(?HandlerInterface $authenticator): ClientBuilder + { + $this->authenticator = $authenticator; + return $this; + } + + /** + * Set your PSR-18 HTTP client. + * + * Service discovery will be used if no client has been provided. + * + * @param \Psr\Http\Client\ClientInterface|null $httpClient + * + * @return ClientBuilder + */ + public function setHttpClient(?ClientInterface $httpClient): ClientBuilder + { + $this->httpClient = $httpClient; + return $this; + } + + /** + * Set debug logger. + * + * The provided logger will be used to record all requests and responses. + * This feature consumes a lot of resources and shouldn't be used in production. + * + * @param \Psr\Log\LoggerInterface|null $debugLogger + * + * @return ClientBuilder + */ + public function setDebugLogger(?LoggerInterface $debugLogger): ClientBuilder + { + $this->debugLogger = $debugLogger; + return $this; + } + + /** + * Set request transformer into API client. + * + * You can use this method to set your request transformer which will execute the pipeline. + * The default request transformer doesn't do anything besides calling provided chain of handlers. + * + * @param \RetailCrm\Api\Interfaces\RequestTransformerInterface|null $requestTransformer + * + * @return ClientBuilder + */ + public function setRequestTransformer(?RequestTransformerInterface $requestTransformer): ClientBuilder + { + $this->requestTransformer = $requestTransformer; + return $this; + } + + /** + * Set response transformer into API client. + * + * You can use this method to set your response transformer which will execute the pipeline. + * The default response transformer doesn't do anything besides calling provided chain of handlers. + * The serializer instance for the request pipeline can be inferred from the provided FormEncoder instance. + * + * @param \RetailCrm\Api\Interfaces\ResponseTransformerInterface|null $responseTransformer + * + * @return ClientBuilder + */ + public function setResponseTransformer(?ResponseTransformerInterface $responseTransformer): ClientBuilder + { + $this->responseTransformer = $responseTransformer; + return $this; + } + + /** + * Set form encoder into API client. + * + * Form encoder is a vital part of the API client. Its purpose is to transform provided request models + * into form-data. The result will be used as a query or POST body (depends on request type). + * + * @param \RetailCrm\Api\Interfaces\FormEncoderInterface $formEncoder + * + * @return ClientBuilder + */ + public function setFormEncoder(FormEncoderInterface $formEncoder): ClientBuilder + { + $this->formEncoder = $formEncoder; + return $this; + } + + /** + * Sets PSR-17 compatible stream factory. You can skip this step if you want to use service discovery. + * + * @param \Psr\Http\Message\StreamFactoryInterface|null $streamFactory + * + * @return ClientBuilder + */ + public function setStreamFactory(?StreamFactoryInterface $streamFactory): ClientBuilder + { + $this->streamFactory = $streamFactory; + return $this; + } + + /** + * Sets PSR-17 compatible request factory. You can skip this step if you want to use service discovery. + * + * @param \Psr\Http\Message\RequestFactoryInterface|null $requestFactory + * + * @return ClientBuilder + */ + public function setRequestFactory(?RequestFactoryInterface $requestFactory): ClientBuilder + { + $this->requestFactory = $requestFactory; + return $this; + } + + /** + * Sets PSR-17 compatible URI factory. You can skip this step if you want to use service discovery. + * + * @param \Psr\Http\Message\UriFactoryInterface|null $uriFactory + * + * @return ClientBuilder + */ + public function setUriFactory(?UriFactoryInterface $uriFactory): ClientBuilder + { + $this->uriFactory = $uriFactory; + return $this; + } + + /** + * Appends an additional request handler into the request processing chain. + * + * @param \RetailCrm\Api\Interfaces\HandlerInterface $handler + * + * @return ClientBuilder + */ + public function appendRequestHandler(HandlerInterface $handler): ClientBuilder + { + $this->requestHandlers[] = $handler; + return $this; + } + + /** + * Appends an additional response handler into the response processing chain. + * + * @param \RetailCrm\Api\Interfaces\HandlerInterface $handler + * + * @return ClientBuilder + */ + public function appendResponseHandler(HandlerInterface $handler): ClientBuilder + { + $this->responseHandlers[] = $handler; + return $this; + } + + /** + * Appends additional request handlers into the request processing chain. + * + * @param \RetailCrm\Api\Interfaces\HandlerInterface[] $handlers + * + * @return ClientBuilder + */ + public function appendRequestHandlers(array $handlers): ClientBuilder + { + foreach ($handlers as $handler) { + $this->appendRequestHandler($handler); + } + + return $this; + } + + /** + * Appends additional response handlers into the response processing chain. + * + * @param \RetailCrm\Api\Interfaces\HandlerInterface[] $handlers + * + * @return ClientBuilder + */ + public function appendResponseHandlers(array $handlers): ClientBuilder + { + foreach ($handlers as $handler) { + $this->appendResponseHandler($handler); + } + + return $this; + } + + /** + * Builds client with provided dependencies. + * + * @inheritDoc + */ + public function build(): Client + { + $this->validateBuilder(); + + if ( + null !== $this->authenticator && + null !== $this->requestTransformer && + null !== $this->requestTransformer->getHandler() + ) { + $this->requestTransformer->getHandler()->append($this->authenticator); + } + + if (null === $this->requestTransformer) { + $this->requestTransformer = $this->buildRequestTransformer(); + } + + if (null === $this->responseTransformer) { + $this->responseTransformer = $this->buildResponseTransformer(); + } + + $this->appendAdditionalRequestHandlers(); + $this->appendAdditionalResponseHandlers(); + + return new Client( + $this->apiUrl, + $this->httpClient ?: Psr18ClientDiscovery::find(), + $this->requestTransformer, // @phpstan-ignore-line + $this->responseTransformer, // @phpstan-ignore-line + $this->streamFactory ?: Psr17FactoryDiscovery::findStreamFactory(), + $this->eventDispatcher, + $this->debugLogger + ); + } + + /** + * Check if builder is ready to build a Client instance. + * + * @throws \RetailCrm\Api\Exception\Client\BuilderException + */ + private function validateBuilder(): void + { + if (empty($this->apiUrl)) { + throw new BuilderException('apiUrl must not be empty', ['apiUrl']); + } + + if (empty($this->authenticator) && empty($this->requestTransformer)) { + throw new BuilderException( + 'Authenticator or RequestTransformer must be present', + ['authenticator', 'requestTransformer'] + ); + } + } + + /** + * Appends additional request handlers into the request and response processor chain (if needed). + * + * @throws \RetailCrm\Api\Exception\Client\BuilderException + */ + private function appendAdditionalRequestHandlers(): void + { + if ( + null !== $this->requestTransformer && + null !== $this->requestTransformer->getHandler() && + count($this->requestHandlers) > 0 + ) { + foreach ($this->requestHandlers as $handler) { + if ($handler instanceof PsrFactoriesAwareInterface) { + $handler->setRequestFactory($this->requestFactory ?: Psr17FactoryDiscovery::findRequestFactory()); + $handler->setStreamFactory($this->streamFactory ?: Psr17FactoryDiscovery::findStreamFactory()); + $handler->setUriFactory($this->uriFactory ?: Psr17FactoryDiscovery::findUriFactory()); + } + } + + $this->requestTransformer->getHandler()->append(static::buildHandlersChain($this->requestHandlers)); + } + } + + /** + * Appends additional response handlers into the request and response processor chain (if needed). + * + * @throws \RetailCrm\Api\Exception\Client\BuilderException + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function appendAdditionalResponseHandlers(): void + { + if ( + null !== $this->responseTransformer && + null !== $this->responseTransformer->getHandler() && + count($this->responseHandlers) > 0 + ) { + foreach ($this->responseHandlers as $handler) { + if ($handler instanceof SerializerAwareInterface && null !== $this->formEncoder) { + $handler->setSerializer($this->formEncoder->getSerializer()); + } + + if ($handler instanceof ApiExceptionFactoryAwareInterface && null !== $this->apiExceptionFactory) { + $handler->setApiExceptionFactory($this->apiExceptionFactory); + } + + if ($handler instanceof EventDispatcherAwareInterface) { + $handler->setEventDispatcher($this->eventDispatcher); + } + } + + $this->responseTransformer->getHandler()->append(static::buildHandlersChain($this->responseHandlers)); + } + } + + /** + * Builds RequestTransformer with default pipeline and authenticator. + * + * @return \RetailCrm\Api\Component\Transformer\RequestTransformer + * @throws \RetailCrm\Api\Exception\Client\BuilderException + */ + private function buildRequestTransformer(): RequestTransformer + { + if (null === $this->formEncoder) { + throw new BuilderException( + "You must provide a FormEncoder instance in order to delegate " . + "RequestTransformer instantiation to the ClientBuilder." + ); + } + + if (null === $this->authenticator) { + throw new BuilderException( + "You must provide an authenticator handler instance in order to delegate " . + "RequestTransformer instantiation to the ClientBuilder." + ); + } + + return new RequestTransformer( + RequestPipelineFactory::createDefaultPipeline( + $this->formEncoder, + $this->uriFactory ?: Psr17FactoryDiscovery::findUriFactory(), + $this->requestFactory ?: Psr17FactoryDiscovery::findRequestFactory(), + $this->streamFactory ?: Psr17FactoryDiscovery::findStreamFactory(), + $this->authenticator + ) + ); + } + + /** + * Builds ResponseTransformer. + * + * @return \RetailCrm\Api\Component\Transformer\ResponseTransformer + * @throws \RetailCrm\Api\Exception\Client\BuilderException + */ + private function buildResponseTransformer(): ResponseTransformer + { + if (null === $this->formEncoder) { + throw new BuilderException( + "You must provide a FormEncoder instance in order to delegate " . + "ResponseTransformer instantiation to the ClientBuilder." + ); + } + + if (null === $this->apiExceptionFactory) { + $this->apiExceptionFactory = new ApiExceptionFactory(); + } + + return new ResponseTransformer(ResponsePipelineFactory::createDefaultPipeline( + $this->formEncoder->getSerializer(), + $this->apiExceptionFactory, + $this->eventDispatcher + )); + } + + /** + * Connect all handlers in the array into chain, return first handler. + * + * @param HandlerInterface[] $handlers + * + * @return \RetailCrm\Api\Interfaces\HandlerInterface + * @throws \RetailCrm\Api\Exception\Client\BuilderException + */ + private static function buildHandlersChain(array $handlers): HandlerInterface + { + if (empty($handlers)) { + throw new BuilderException('Supplied handlers chain must contain at least one handler'); + } + + if (1 === count($handlers)) { + return $handlers[0]; + } + + for ($i = 0, $iMax = count($handlers) - 1; $i < $iMax; $i++) { + $handlers[$i]->setNext($handlers[$i + 1]); + } + + return $handlers[0]; + } +} diff --git a/src/Builder/FilesystemCacheBuilder.php b/src/Builder/FilesystemCacheBuilder.php new file mode 100644 index 0000000..7b322b6 --- /dev/null +++ b/src/Builder/FilesystemCacheBuilder.php @@ -0,0 +1,89 @@ +cacheDir = $cacheDir; + return $this; + } + + /** + * Builds and returns filesystem cache. + * + * @return CacheItemPoolInterface + * @inheritDoc + */ + public function build(): CacheItemPoolInterface + { + if (empty($this->cacheDir)) { + return new FilesystemAdapter(CacheDirectories::DIR_NAME); + } + + $cacheDir = static::getCacheDirPath($this->cacheDir); + $this->createDir($cacheDir); + + return new FilesystemAdapter('', 0, $cacheDir); + } + + /** + * @param string $dir + * + * @throws \RetailCrm\Api\Exception\Client\BuilderException + */ + private function createDir(string $dir): void + { + if (is_dir($dir)) { + return; + } + + if (false === mkdir($dir, 0777, true) && false === is_dir($dir)) { + throw new BuilderException(sprintf('Could not create directory "%s".', $dir)); + } + } + + /** + * Returns target cache dir for cache. + * + * @param string $cacheDir + * + * @return string + */ + private static function getCacheDirPath(string $cacheDir): string + { + if ('' !== $cacheDir && DIRECTORY_SEPARATOR !== $cacheDir[strlen($cacheDir) - 1]) { + $cacheDir .= DIRECTORY_SEPARATOR; + } + + return $cacheDir . CacheDirectories::MAIN_DIR . DIRECTORY_SEPARATOR; + } +} diff --git a/src/Builder/FormEncoderBuilder.php b/src/Builder/FormEncoderBuilder.php new file mode 100644 index 0000000..2df5402 --- /dev/null +++ b/src/Builder/FormEncoderBuilder.php @@ -0,0 +1,120 @@ +fsCacheBuilder = new FilesystemCacheBuilder(); + } + + /** + * Sets cache directory. + * + * This directory will be used by FormEncoder component and underlying serializer to store annotations cache. + * Annotations parsing consumes a lot of resources, which is the reason why you should cache results. + * + * @param string $cacheDir + * + * @return FormEncoderBuilder + */ + public function setCacheDir(string $cacheDir): FormEncoderBuilder + { + $this->fsCacheBuilder->setCacheDir($cacheDir); + return $this; + } + + /** + * Sets cache implementation. + * + * This cache implementation will be used by FormEncoder component and underlying serializer to store + * annotations cache. Any cache from `symfony/cache` should work just fine. + * + * @param \Psr\Cache\CacheItemPoolInterface $cache + * + * @return FormEncoderBuilder + */ + public function setCache(CacheItemPoolInterface $cache): FormEncoderBuilder + { + $this->cache = $cache; + return $this; + } + + /** + * Builds FormEncoder. + * + * **Note:** Cache won't be set into provided serializer instance. It only works for instance built by + * this builder. You must manually inject the proper cache into the custom JMS Serializer instance. + * + * @inheritDoc + */ + public function build(): FormEncoderInterface + { + $this->buildCache(); + $this->buildAnnotationReader(); + + $serializer = SerializerFactory::create(); + + return new FormEncoder($serializer, $this->annotationReader); + } + + /** + * Builds cache if needed. + * + * @throws \RetailCrm\Api\Exception\Client\BuilderException + */ + private function buildCache(): void + { + if (null === $this->cache) { + $this->cache = $this->fsCacheBuilder->build(); + } + } + + /** + * Builds annotation reader. + */ + private function buildAnnotationReader(): void + { + $this->annotationReader = new AnnotationReader(); + + if (null !== $this->cache) { + $this->annotationReader = new PsrCachedReader(new AnnotationReader(), $this->cache); + } + } +} diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 0000000..8ea9d13 --- /dev/null +++ b/src/Client.php @@ -0,0 +1,343 @@ +streamFactory = $streamFactory; + + $this->api = new Api( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->costs = new Costs( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->customFields = new CustomFields( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->customers = new Customers( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->customersCorporate = new CustomersCorporate( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->delivery = new Delivery( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->files = new Files( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->integration = new Integration( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->loyalty = new Loyalty( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->orders = new Orders( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->packs = new Packs( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->payments = new Payments( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->references = new References( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->segments = new Segments( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->settings = new Settings( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->store = new Store( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->tasks = new Tasks( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->telephony = new Telephony( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->users = new Users( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->verification = new Verification( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + $this->statistics = new Statistics( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); + } + + /** + * Returns PSR-17 stream factory. + * + * StreamFactory can be used to create a PSR-7 StreamInterface from various sources. + * + * @return \Psr\Http\Message\StreamFactoryInterface + */ + public function getStreamFactory(): StreamFactoryInterface + { + return $this->streamFactory; + } + + /** + * Parses provided URL, builds API url with version out of it. + * + * @param string $url + * + * @return string + */ + private static function getBaseUrl(string $url): string + { + return Utils::removeTrailingSlash($url) . '/api/v5'; + } +} diff --git a/src/Command/AbstractModelsProcessorCommand.php b/src/Command/AbstractModelsProcessorCommand.php new file mode 100644 index 0000000..908b9f7 --- /dev/null +++ b/src/Command/AbstractModelsProcessorCommand.php @@ -0,0 +1,54 @@ +getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE; + } + + /** + * Create directory + * + * @param string $dir + * + * @throws RuntimeException + */ + protected static function createDir(string $dir): void + { + if (is_dir($dir)) { + return; + } + + if (false === mkdir($dir, 0777, true) && false === is_dir($dir)) { + throw new RuntimeException(sprintf('Could not create directory "%s".', $dir)); + } + } +} diff --git a/src/Command/ClearModelsCommand.php b/src/Command/ClearModelsCommand.php new file mode 100644 index 0000000..5845662 --- /dev/null +++ b/src/Command/ClearModelsCommand.php @@ -0,0 +1,86 @@ +setName('models:clear') + ->setDescription('Removes all generated models, leaves only empty directory behind.') + ->setHelp('Use this command if you want to remove existing model cache.'); + } + + /** + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $target = Utils::getModelsCacheDirectory(); + + if (!is_dir($target)) { + $output->writeln('The target directory does not exist.'); + $output->writeln('You can safely use "bin/console models:generate" to generate models.'); + + return -1; + } + + $output->writeln(sprintf('Cleaning up "%s"...', $target)); + + if (self::isVerbose($output)) { + $output->writeln(''); + } + + $checksumFile = implode(DIRECTORY_SEPARATOR, [$target, 'checksum.json']); + $models = new PhpFilesIterator($target); + + foreach ($models as $model) { + if (file_exists($model['file'])) { + unlink($model['file']); + + if (self::isVerbose($output)) { + $output->writeln(sprintf('- Removed "%s"', $model['file'])); + } + } + } + + if (file_exists($checksumFile)) { + unlink($checksumFile); + } + + if (self::isVerbose($output)) { + $output->writeln(''); + } + + $output->writeln(' ✓ Done!'); + + return 0; + } +} diff --git a/src/Command/CompilerPromptCommand.php b/src/Command/CompilerPromptCommand.php new file mode 100644 index 0000000..484d9a4 --- /dev/null +++ b/src/Command/CompilerPromptCommand.php @@ -0,0 +1,171 @@ +setName('compiler:prompt') + ->setDescription('Enable or disable composer compiler prompt.') + ->setHelp('Use this command to suppress the compiler message and enable automatic compilation.') + ->addOption( + 'deactivate', + 'd', + InputOption::VALUE_OPTIONAL, + 'Hide compiler prompt and run compiler task automatically. This mode is used by default.', + false + ) + ->addOption( + 'activate', + 'a', + InputOption::VALUE_OPTIONAL, + 'Show compiler prompt and only run compiler task if user allows it.', + false + ); + } + + /** + * Execute the command. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composerJson = ComposerLocator::findComposerJson(); + + if ('' === $composerJson) { + $output->writeln(' ❌ Cannot find composer.json'); + + return -1; + } + + try { + $json = json_decode((string) file_get_contents($composerJson), true, 512, JSON_THROW_ON_ERROR); + } catch (JsonException $exception) { + $output->writeln(sprintf(' ❌ Invalid JSON: %s', $exception->getMessage())); + + return -1; + } + + $activatePrompt = false !== $input->getOption('activate'); + + if ($activatePrompt) { + static::activatePrompt($json); + } else { + static::deactivatePrompt($json); + } + + try { + $result = json_encode($json, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } catch (JsonException $exception) { + $output->writeln(sprintf(' ❌ Cannot encode JSON: %s', $exception->getMessage())); + + return -1; + } + + if (false === file_put_contents($composerJson, $result)) { + $output->writeln(' ❌ Cannot write to file.'); + + return -1; + } + + $output->writeln(sprintf( + ' ✓ Done, generator prompt is now %s.', + $activatePrompt ? 'enabled' : 'disabled' + )); + + return 0; + } + + /** + * Activate prompt in the provided composer.json + * + * @param array $composerJson + */ + private static function activatePrompt(array &$composerJson): void + { + if (!array_key_exists('extra', $composerJson)) { + $composerJson['extra'] = []; + } + + if ( + array_key_exists('compile-whitelist', $composerJson['extra']) && + is_array($composerJson['extra']['compile-whitelist']) && + in_array(static::PACKAGE_NAME, $composerJson['extra']['compile-whitelist'], true) + ) { + $composerJson['extra']['compile-whitelist'] = array_filter( + $composerJson['extra']['compile-whitelist'], + static function ($value) { + return static::PACKAGE_NAME !== $value; + } + ); + } + + if ( + empty($composerJson['extra']['compile-whitelist']) && + array_key_exists('compile-mode', $composerJson['extra']) && + 'whitelist' === $composerJson['extra']['compile-mode'] + ) { + unset($composerJson['extra']['compile-whitelist'], $composerJson['extra']['compile-mode']); + } + + if (1 === count($composerJson['extra'])) { + unset($composerJson['extra']); + } + } + + /** + * Deactivate prompt in the provided composer.json + * + * @param array $composerJson + * + * @SuppressWarnings(PHPMD.ElseExpression) + */ + private static function deactivatePrompt(array &$composerJson): void + { + if (!array_key_exists('extra', $composerJson)) { + $composerJson['extra'] = []; + } + + if ( + array_key_exists('compile-whitelist', $composerJson['extra']) && + is_array($composerJson['extra']['compile-whitelist']) && + !in_array(static::PACKAGE_NAME, $composerJson['extra']['compile-whitelist'], true) + ) { + $composerJson['extra']['compile-whitelist'][] = static::PACKAGE_NAME; + } else { + $composerJson['extra']['compile-whitelist'] = [static::PACKAGE_NAME]; + } + + $composerJson['extra']['compile-mode'] = 'whitelist'; + } +} diff --git a/src/Command/GenerateModelsCommand.php b/src/Command/GenerateModelsCommand.php new file mode 100644 index 0000000..64d70c5 --- /dev/null +++ b/src/Command/GenerateModelsCommand.php @@ -0,0 +1,94 @@ +setName('models:generate') + ->setDescription('Converts all JMS models to static (de)serialization code.') + ->setHelp('Use this command after making any changes to the models.') + ->addOption( + 'all', + 'a', + InputOption::VALUE_OPTIONAL, + 'Generate cache for all models instead of changed models only.', + false + ); + } + + /** + * Execute the command. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $verbose = static::isVerbose($output); + $generator = new ModelsGenerator(false !== $input->getOption('all')); + + $output->writeln('Preparing a list of models to generate cache files...'); + $output->writeln( + 'Note: Request models will be omitted ' . + 'because they are being handled by FormEncoder.' + ); + $output->writeln(''); + + $generator->loadModelsList(); + + if ($verbose) { + foreach ($generator->getModels() as $model) { + $output->writeln(sprintf('- Added %s', $model)); + } + } + + if ($verbose && count($generator->getModels()) > 0) { + $output->writeln(''); + } + + if (count($generator->getModels()) === 0) { + $output->writeln('No changes were found; skipping generation...'); + $output->writeln(''); + } + + $generator->generate(); + + $output->writeln(sprintf( + ' ✓ Done, generated code for %d models.', + count($generator->getModels()) + )); + + return 0; + } +} diff --git a/src/Command/VerifyModelsCommand.php b/src/Command/VerifyModelsCommand.php new file mode 100644 index 0000000..493ab84 --- /dev/null +++ b/src/Command/VerifyModelsCommand.php @@ -0,0 +1,57 @@ +setName('models:verify') + ->setDescription('Verify models cache. This command will fail if model cache is not up-to-date.') + ->setHelp('Use this command if you want to check existing model cache.'); + } + + /** + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * + * @return int + * @throws \JsonException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + if (ModelsChecksumGenerator::verifyChecksum()) { + $io->success("Models are up to date."); + return 0; + } + + $io->error("Outdated models! Run \"models:generate\" command to fix that."); + return -1; + } +} diff --git a/src/Component/ComposerLocator.php b/src/Component/ComposerLocator.php new file mode 100644 index 0000000..c9b9715 --- /dev/null +++ b/src/Component/ComposerLocator.php @@ -0,0 +1,83 @@ + */ + private $iterator; + + /** @var callable */ + private $fileNameAccessor; + + /** @var callable */ + private $keyTransformer; + + /** @var callable */ + private $hashFunc; + + /** + * FilesIteratorChecksumGenerator constructor. + * + * @param Iterator $iterator + */ + public function __construct(Iterator $iterator) + { + $this->iterator = $iterator; + } + + /** + * @return callable + */ + public function getFileNameAccessor(): callable + { + return $this->fileNameAccessor ?? static function ($fileName) { + return (string) $fileName; + }; + } + + /** + * @param callable $fileNameAccessor + * + * @return FilesIteratorChecksumGenerator + */ + public function setFileNameAccessor(callable $fileNameAccessor): FilesIteratorChecksumGenerator + { + $this->fileNameAccessor = $fileNameAccessor; + return $this; + } + + /** + * @return callable + */ + public function getKeyTransformer(): callable + { + return $this->keyTransformer ?? static function ($fileName) { + return (string) $fileName; + }; + } + + /** + * This callable will be used to provide key for the hashes array. + * + * @param callable $keyTransformer + * + * @return FilesIteratorChecksumGenerator + */ + public function setKeyTransformer(callable $keyTransformer): FilesIteratorChecksumGenerator + { + $this->keyTransformer = $keyTransformer; + return $this; + } + + /** + * @return callable + */ + public function getHashFunc(): callable + { + return $this->hashFunc ?? static function ($fileName) { + $contents = preg_replace('/\r|\n|\r\n/m', "\n", (string) file_get_contents($fileName)); + return md5($contents ?? ''); + }; + } + + /** + * This function will receive file name and should return file hash. + * + * @param callable $hashFunc + * + * @return FilesIteratorChecksumGenerator + */ + public function setHashFunc(callable $hashFunc): FilesIteratorChecksumGenerator + { + $this->hashFunc = $hashFunc; + return $this; + } + + + /** + * Generate hash string from the contents of a directory. + * + * @return array + */ + public function generateHashes(): array + { + $hashes = []; + + foreach ($this->iterator as $item) { + $fileName = $this->getFileNameAccessor()($item); + $hashes[(string) $this->getKeyTransformer()($item)] = $this->getHashFunc()($fileName); + } + + return $hashes; + } +} diff --git a/src/Component/FormData/FormEncoder.php b/src/Component/FormData/FormEncoder.php new file mode 100644 index 0000000..f2f2d73 --- /dev/null +++ b/src/Component/FormData/FormEncoder.php @@ -0,0 +1,131 @@ +serializer = $serializer; + $this->annotationReader = $annotationReader ?: new AnnotationReader(); + } + + /** + * Encodes provided object into a form data + * + * @param mixed $object + * @param string $type + * + * @return string + * @throws \ReflectionException + */ + public function encode($object, string $type = ''): string + { + return http_build_query($this->encodeArray($object, $type)); + } + + /** + * Encodes provided object into an array + * + * @param mixed $object + * @param string $type + * + * @return array + * @throws \ReflectionException + */ + public function encodeArray($object, string $type = ''): array + { + $type = empty($type) ? gettype($object) : $type; + $result = (array) StrategyFactory::encodeStrategyByType( + $type, + $object, + $this->annotationReader, + $this->serializer + )->encode($object, null); + + return $this->processPostSerialize($object, $result); + } + + /** + * Returns underlying serializer instance. + * + * @return \Liip\Serializer\SerializerInterface + */ + public function getSerializer(): SerializerInterface + { + return $this->serializer; + } + + /** + * Process post deserialize callback + * + * @param mixed $object + * @param mixed[] $result + * + * @return mixed[] + * @throws ReflectionException + */ + private function processPostSerialize($object, array $result): array + { + $class = get_class($object); + + if (false !== $object) { + try { + $reflection = new ReflectionClass($class); + } catch (ReflectionException $e) { + return $result; + } + + foreach ($reflection->getMethods() as $method) { + $postDeserialize = $this->annotationReader + ->getMethodAnnotation($method, PostSerialize::class); + + if ($postDeserialize instanceof PostSerialize) { + return $method->invokeArgs($object, [$result]); + } + } + } + + return $result; + } +} diff --git a/src/Component/FormData/Mapping/Accessor.php b/src/Component/FormData/Mapping/Accessor.php new file mode 100644 index 0000000..c1dbe29 --- /dev/null +++ b/src/Component/FormData/Mapping/Accessor.php @@ -0,0 +1,45 @@ + + * @license https://retailcrm.ru Proprietary + * @link http://retailcrm.ru + * @see https://help.retailcrm.ru + */ +class PropertyAnnotations +{ + /** + * @var SerializedName|null + */ + public $serializedName; + + /** + * @var Accessor|null + */ + public $accessor; + + /** + * @var Type|null + */ + public $type; + + /** + * @var JsonField|null + */ + public $jsonField; + + /** + * PropertyAnnotations constructor. + * + * @param object[] $annotations + */ + public function __construct(array $annotations = []) + { + foreach ($annotations as $annotation) { + switch (get_class($annotation)) { + case Type::class: + $this->type = $annotation; + break; + case SerializedName::class: + $this->serializedName = $annotation; + break; + case Accessor::class: + $this->accessor = $annotation; + break; + case JsonField::class: + $this->jsonField = $annotation; + break; + } + } + } +} diff --git a/src/Component/FormData/Strategy/Encode/AbstractEncodeStrategy.php b/src/Component/FormData/Strategy/Encode/AbstractEncodeStrategy.php new file mode 100644 index 0000000..630495d --- /dev/null +++ b/src/Component/FormData/Strategy/Encode/AbstractEncodeStrategy.php @@ -0,0 +1,56 @@ +annotationReader = $annotationReader; + $this->liipSerializer = $liipSerializer; + } + + /** + * Sets inner type for types like array and \DateTime + * + * @param string $type + * + * @return \RetailCrm\Api\Component\FormData\Strategy\Encode\EncodeStrategyInterface + */ + public function setInnerType(string $type): EncodeStrategyInterface + { + $this->innerType = $type; + return $this; + } +} diff --git a/src/Component/FormData/Strategy/Encode/DateTimeStrategy.php b/src/Component/FormData/Strategy/Encode/DateTimeStrategy.php new file mode 100644 index 0000000..7d564dc --- /dev/null +++ b/src/Component/FormData/Strategy/Encode/DateTimeStrategy.php @@ -0,0 +1,34 @@ +format($this->innerType); + } + + return null; + } +} diff --git a/src/Component/FormData/Strategy/Encode/EncodeStrategyInterface.php b/src/Component/FormData/Strategy/Encode/EncodeStrategyInterface.php new file mode 100644 index 0000000..ae5f963 --- /dev/null +++ b/src/Component/FormData/Strategy/Encode/EncodeStrategyInterface.php @@ -0,0 +1,44 @@ + + * @license MIT + * @link http://retailcrm.ru + * @see http://retailcrm.ru/docs + */ + +namespace RetailCrm\Api\Component\FormData\Strategy\Encode; + +use RetailCrm\Api\Component\FormData\PropertyAnnotations; + +/** + * Interface EncodeStrategyInterface + * + * @internal + * @package RetailCrm\Api\Component\FormData\Strategy\Encode + */ +interface EncodeStrategyInterface +{ + /** + * Serialize value + * + * @param mixed $value + * @param \RetailCrm\Api\Component\FormData\PropertyAnnotations|null $annotations + * + * @return mixed + */ + public function encode($value, ?PropertyAnnotations $annotations); + + /** + * Sets inner type for types like array and \DateTime + * + * @param string $type + * + * @return \RetailCrm\Api\Component\FormData\Strategy\Encode\EncodeStrategyInterface + */ + public function setInnerType(string $type): EncodeStrategyInterface; +} diff --git a/src/Component/FormData/Strategy/Encode/EntityStrategy.php b/src/Component/FormData/Strategy/Encode/EntityStrategy.php new file mode 100644 index 0000000..218463c --- /dev/null +++ b/src/Component/FormData/Strategy/Encode/EntityStrategy.php @@ -0,0 +1,117 @@ +jsonField instanceof JsonField) { + return $this->liipSerializer->serialize($value, 'json'); + } + + $result = []; + $reflection = new ReflectionClass(get_class($value)); + + if (!$reflection->isUserDefined()) { + return (array) $value; + } + + foreach ($reflection->getProperties() as $property) { + $this->encodeProperty($value, $property, $result); + } + + return $result; + } + + /** + * @param mixed $object + * @param \ReflectionProperty $property + * @param mixed[] $result + * + * @SuppressWarnings(PHPMD.ElseExpression) + */ + protected function encodeProperty($object, \ReflectionProperty $property, array &$result): void + { + $annotations = new PropertyAnnotations($this->annotationReader->getPropertyAnnotations($property)); + + if (!($annotations->serializedName instanceof SerializedName)) { + return; + } + + if ($annotations->accessor instanceof Accessor && !empty($annotations->accessor->getter)) { + $value = $object->{$annotations->accessor->getter}(); + } else { + $property->setAccessible(true); + $value = $property->getValue($object); + } + + if ($this->isNoTransform($property)) { + $result[$annotations->serializedName->name] = $value; + } elseif ($annotations->type instanceof Type) { + $result[$annotations->serializedName->name] = + StrategyFactory::encodeStrategyByType( + $annotations->type->type, + $value, + $this->annotationReader, + $this->liipSerializer + )->encode($value, $annotations); + } else { + $result[$annotations->serializedName->name] = + StrategyFactory::encodeStrategyByType( + gettype($value), + $value, + $this->annotationReader, + $this->liipSerializer + )->encode($value, $annotations); + } + } + + /** + * Returns true if NoTransform annotation was used + * + * @param \ReflectionProperty $property + * + * @return bool + */ + protected function isNoTransform(\ReflectionProperty $property): bool + { + $isNoTransform = $this->annotationReader->getPropertyAnnotation($property, NoTransform::class); + + return $isNoTransform instanceof NoTransform; + } +} diff --git a/src/Component/FormData/Strategy/Encode/SimpleTypeStrategy.php b/src/Component/FormData/Strategy/Encode/SimpleTypeStrategy.php new file mode 100644 index 0000000..306f1d4 --- /dev/null +++ b/src/Component/FormData/Strategy/Encode/SimpleTypeStrategy.php @@ -0,0 +1,86 @@ +jsonField instanceof JsonField && !empty($value)) { + return $this->liipSerializer->serialize($value, 'json'); + } + + return $this->encodeValue($value); + } + + /** + * Encode simple value. + * + * @param mixed $value + * + * @return bool|float|int|mixed[]|string|null + */ + private function encodeValue($value) + { + switch (gettype($value)) { + case 'bool': + case 'boolean': + return (bool) $value; + case 'int': + case 'integer': + return (int) $value; + case 'float': + return (float) $value; + case 'double': + return (double) $value; + case 'string': + return (string) $value; + default: + return $this->encodeDefault($value); + } + } + + /** + * @param mixed $value + * + * @return mixed[]|null + */ + private function encodeDefault($value): ?array + { + if (is_iterable($value)) { + $result = []; + + foreach ($value as $key => $item) { + $result[$key] = StrategyFactory::encodeStrategyByType( + gettype($item), + $item, + $this->annotationReader, + $this->liipSerializer + )->encode($item, new PropertyAnnotations()); + } + + return $result; + } + + return null; + } +} diff --git a/src/Component/FormData/Strategy/Encode/StreamInterfaceStrategy.php b/src/Component/FormData/Strategy/Encode/StreamInterfaceStrategy.php new file mode 100644 index 0000000..3fbcec4 --- /dev/null +++ b/src/Component/FormData/Strategy/Encode/StreamInterfaceStrategy.php @@ -0,0 +1,38 @@ +isSeekable()) { + $value->seek(0); + } + + return $value->getContents(); + } + + return null; + } +} diff --git a/src/Component/FormData/Strategy/Encode/TypedArrayStrategy.php b/src/Component/FormData/Strategy/Encode/TypedArrayStrategy.php new file mode 100644 index 0000000..504ef06 --- /dev/null +++ b/src/Component/FormData/Strategy/Encode/TypedArrayStrategy.php @@ -0,0 +1,121 @@ +)/m'; + + /** @var \RetailCrm\Api\Component\FormData\Strategy\Encode\SimpleTypeStrategy */ + private $simpleStrategy; + + /** + * TypedArrayStrategy constructor. + * + * @param \Doctrine\Common\Annotations\Reader $annotationReader + * @param \Liip\Serializer\SerializerInterface $liipSerializer + */ + public function __construct(Reader $annotationReader, SerializerInterface $liipSerializer) + { + parent::__construct($annotationReader, $liipSerializer); + + $this->simpleStrategy = new SimpleTypeStrategy($this->annotationReader, $this->liipSerializer); + } + + /** + * @param mixed $value + * @param \RetailCrm\Api\Component\FormData\PropertyAnnotations|null $annotations + * + * @return mixed[]|mixed + */ + public function encode($value, ?PropertyAnnotations $annotations = null) + { + if (!is_array($value)) { + return $value; + } + + if (strpos($this->innerType, ',') !== false) { + [$keyType, $valueType] = static::getInnerTypes($this->innerType); + + if ('' === $keyType && '' === $valueType) { + $valueType = $this->innerType; + } + } else { + $valueType = $this->innerType; + } + + if (null !== $annotations && $annotations->jsonField instanceof JsonField && !empty($value)) { + return $this->liipSerializer->serialize($value, 'json'); + } + + return $this->encodeRegularArray($value, $valueType); + } + + /** + * Encode regular typed array. + * + * @param array $value + * @param string $valueType + * + * @return array + */ + private function encodeRegularArray(array $value, string $valueType): array + { + $result = []; + + foreach (array_keys($value) as $key) { + $result[$this->simpleStrategy->encode($key, new PropertyAnnotations())] + = StrategyFactory::encodeStrategyByType( + $valueType, + $value[$key], + $this->annotationReader, + $this->liipSerializer + )->encode($value[$key], new PropertyAnnotations()); + } + + return $result; + } + + /** + * Returns inner types for array with typed key (example: array>). + * + * @param string $innerType + * + * @return string[] + */ + private static function getInnerTypes(string $innerType): array + { + $matches = []; + + preg_match_all(static::$innerTypesMatcher, $innerType, $matches, PREG_SET_ORDER, 0); + + if (empty($matches)) { + return ['', '']; + } + + $matches = $matches[0]; + + return [trim($matches[1]), trim($matches[2])]; + } +} diff --git a/src/Component/FormData/Strategy/StrategyFactory.php b/src/Component/FormData/Strategy/StrategyFactory.php new file mode 100644 index 0000000..2c466f6 --- /dev/null +++ b/src/Component/FormData/Strategy/StrategyFactory.php @@ -0,0 +1,159 @@ +$/m'; + + /** @var string[] $simpleTypes */ + private static $simpleTypes = [ + 'bool', + 'boolean', + 'int', + 'integer', + 'float', + 'double', + 'string', + 'array' + ]; + + /** + * Returns encode strategy for provided type + * + * @param string $dataType + * @param mixed $value + * @param \Doctrine\Common\Annotations\Reader $annotationReader + * @param \Liip\Serializer\SerializerInterface $serializer + * + * @return \RetailCrm\Api\Component\FormData\Strategy\Encode\EncodeStrategyInterface + */ + public static function encodeStrategyByType( + string $dataType, + $value, + Reader $annotationReader, + SerializerInterface $serializer + ): EncodeStrategyInterface { + if (in_array($dataType, static::$simpleTypes)) { + return new Encode\SimpleTypeStrategy($annotationReader, $serializer); + } + + if (static::isDateTime($dataType)) { + return (new Encode\DateTimeStrategy($annotationReader, $serializer))->setInnerType(DateTime::RFC3339); + } + + $arrSubType = static::getArrayInnerTypes($dataType); + + if (!empty($arrSubType)) { + return (new Encode\TypedArrayStrategy($annotationReader, $serializer))->setInnerType($arrSubType); + } + + $dateTimeFormat = static::getDateTimeFormat($dataType); + + if (!empty($dateTimeFormat)) { + return (new Encode\DateTimeStrategy($annotationReader, $serializer))->setInnerType($dateTimeFormat); + } + + if ($value instanceof StreamInterface) { + return new Encode\StreamInterfaceStrategy($annotationReader, $serializer); + } + + return new Encode\EntityStrategy($annotationReader, $serializer); + } + + /** + * Returns true if provided type is DateTime + * + * @param string $dataType + * + * @return bool + */ + private static function isDateTime(string $dataType): bool + { + return strlen($dataType) > 1 + && (DateTime::class === $dataType + || ('\\' === $dataType[0] && DateTime::class === substr($dataType, 1))); + } + + /** + * Returns array inner type for arrays like array> + * For this example, "int, \DateTime" will be returned. + * + * Also works for arrays like int[]. + * + * @param string $dataType + * + * @return string + */ + private static function getArrayInnerTypes(string $dataType): string + { + $matches = []; + + preg_match_all(static::TYPED_MATCHER, $dataType, $matches, PREG_SET_ORDER, 0); + + if (empty($matches)) { + if (strlen($dataType) > 2 && substr($dataType, -2) === '[]') { + return substr($dataType, 0, -2); + } + + return ''; + } + + if ($matches[0][1] === 'array') { + return $matches[0][2]; + } + + return ''; + } + + /** + * Returns DateTime format. Example: \DateTime> + * + * @param string $dataType + * + * @return string + */ + private static function getDateTimeFormat(string $dataType): string + { + $matches = []; + + preg_match_all(static::TYPED_MATCHER, $dataType, $matches, PREG_SET_ORDER, 0); + + if (empty($matches)) { + return ''; + } + + if ($matches[0][1] === 'DateTime') { + $format = $matches[0][2]; + + if (strlen($format) > 2 && $format[0] === "'" && substr($format, -1) === "'") { + return substr($format, 1, -1); + } + + return $format; + } + + return ''; + } +} diff --git a/src/Component/ModelsGenerator.php b/src/Component/ModelsGenerator.php new file mode 100644 index 0000000..7e76185 --- /dev/null +++ b/src/Component/ModelsGenerator.php @@ -0,0 +1,235 @@ + */ + private $oldChecksums; + + /** @var array */ + private $newChecksums; + + /** @var bool */ + private $generateAll; + + /** + * ModelsGenerator constructor. + * + * @param bool $generateAll + */ + public function __construct(bool $generateAll) + { + $this->generateAll = $generateAll; + } + + public function generate(): void + { + $target = Utils::getModelsCacheDirectory(); + $this->calculateChecksums(); + + if (!is_dir($target)) { + static::createDir($target); + file_put_contents(implode(DIRECTORY_SEPARATOR, [$target, '.gitkeep']), ''); + } + + self::generateModelCache($this->models, $target); + ModelsChecksumGenerator::saveChecksums($this->newChecksums); + } + + /** + * Returns a list of the models present in the library. + */ + public function loadModelsList(): void + { + $this->models = []; + $classes = new PhpFilesIterator(DevUtils::getModelsDirectory()); + + foreach ($classes as $model) { + if (!array_key_exists('fqn', $model)) { + continue; + } + + if ( + !static::isNamespaceIgnored($model['fqn']) && + $this->shouldGenerateForModel($model['fqn']) + ) { + $this->models[] = $model['fqn']; + } + } + } + + /** + * @return string[] + */ + public function getModels(): array + { + return $this->models; + } + + /** + * Calculate models checksums + */ + private function calculateChecksums(): void + { + $this->newChecksums = ModelsChecksumGenerator::generateChecksums(); + + if (!$this->generateAll) { + $this->oldChecksums = ModelsChecksumGenerator::getStoredChecksums(); + } + } + + /** + * Returns true if cache for model should be generated. + * + * @param string $className + * + * @return bool + */ + private function shouldGenerateForModel(string $className): bool + { + if ($this->generateAll) { + return true; + } + + $serializerFile = SerializerGenerator::buildSerializerFunctionName($className, null, []) . '.php'; + $deserializerFile = DeserializerGenerator::buildDeserializerFunctionName($className) . '.php'; + + if ( + !is_file(implode(DIRECTORY_SEPARATOR, [Utils::getModelsCacheDirectory(), $serializerFile])) || + !is_file(implode(DIRECTORY_SEPARATOR, [Utils::getModelsCacheDirectory(), $deserializerFile])) + ) { + return true; + } + + return ( + isset($this->oldChecksums[$className]) && + $this->oldChecksums[$className] !== $this->newChecksums[$className] + ); + } + + /** + * Generate models cache. + * + * @param string[] $classes + * @param string $target + * + * @throws \Exception + */ + private static function generateModelCache(array $classes, string $target): void + { + if (empty($classes)) { + return; + } + + $configurationArray = [ + 'default_group_combinations' => [], + 'default_versions' => [], + 'classes' => [], + ]; + + foreach ($classes as $class) { + $configurationArray['classes'][$class] = []; + } + + $configuration = GeneratorConfiguration::createFomArray($configurationArray); + $parsers = [new JMSParser(new AnnotationReader())]; + $builder = new Builder(new Parser($parsers), new RecursionChecker(null, [])); + + $marshalGenerator = new SerializerGenerator( + new Serialization(), + new CustomSerialization(), + $configuration, + $target + ); + $unmarshalGenerator = new DeserializerGenerator( + new Deserialization(), + new CustomDeserialization(), + $classes, + $target + ); + $marshalGenerator->generate($builder); + $unmarshalGenerator->generate($builder); + } + + /** + * Returns true if models in provided namespace should be ignored. + * + * @param string $namespace + * + * @return bool + */ + private static function isNamespaceIgnored(string $namespace): bool + { + foreach (static::IGNORED_NAMESPACES as $ignoredNamespace) { + if (false !== strpos($namespace, $ignoredNamespace)) { + return true; + } + } + + return false; + } + + /** + * Create directory + * + * @param string $dir + * + * @throws RuntimeException + */ + private static function createDir(string $dir): void + { + if (is_dir($dir)) { + return; + } + + if (false === mkdir($dir, 0777, true) && false === is_dir($dir)) { + throw new RuntimeException(sprintf('Could not create directory "%s".', $dir)); + } + } +} diff --git a/src/Component/PhpFilesIterator.php b/src/Component/PhpFilesIterator.php new file mode 100644 index 0000000..a088975 --- /dev/null +++ b/src/Component/PhpFilesIterator.php @@ -0,0 +1,109 @@ +> + */ +class PhpFilesIterator implements Iterator +{ + private const NAMESPACE_MATCHER = '/namespace\s+((?:\\\\{1,2}\w+|\w+\\\\{1,2})(?:\w+\\\\{0,2})+)/m'; + + /** @var Iterator */ + private $parent; + + /** + * PhpFilesIterator constructor. + * + * @param string $directory + */ + public function __construct(string $directory) + { + $this->parent = new RegexIterator( + new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory)), + '/^.+\.php$/i', + RecursiveRegexIterator::GET_MATCH + ); + } + + /** + * @inheritDoc + * + * @return array + */ + public function current(): array + { + $matches = []; + $file = $this->parent->current(); + + if (is_array($file) && count($file) > 0) { + $file = $file[0]; + } + + if (empty($file)) { + return []; + } + + $result = ['file' => $file]; + + preg_match(static::NAMESPACE_MATCHER, (string) file_get_contents($file), $matches); + + if (count($matches) >= 2) { + $result['namespace'] = $matches[1]; + $result['fqn'] = sprintf('%s\\%s', $matches[1], str_ireplace('.php', '', basename($file))); + } + + return $result; + } + + /** + * @inheritDoc + */ + public function next(): void + { + $this->parent->next(); + } + + /** + * @inheritDoc + * + * @return int|string + */ + public function key() + { + return $this->parent->key(); + } + + /** + * @inheritDoc + */ + public function valid(): bool + { + return $this->parent->valid(); + } + + /** + * @inheritDoc + */ + public function rewind(): void + { + $this->parent->rewind(); + } +} diff --git a/src/Component/Serializer/Annotation/AccessType.php b/src/Component/Serializer/Annotation/AccessType.php new file mode 100644 index 0000000..17bcdef --- /dev/null +++ b/src/Component/Serializer/Annotation/AccessType.php @@ -0,0 +1,21 @@ + + * @internal + */ +final class AccessType +{ + /** + * @Required + * @var string + */ + public $type; +} diff --git a/src/Component/Serializer/Annotation/Accessor.php b/src/Component/Serializer/Annotation/Accessor.php new file mode 100644 index 0000000..c15c58a --- /dev/null +++ b/src/Component/Serializer/Annotation/Accessor.php @@ -0,0 +1,25 @@ + + * @internal + */ +final class Accessor +{ + /** + * @var string + */ + public $getter; + + /** + * @var string + */ + public $setter; +} diff --git a/src/Component/Serializer/Annotation/AccessorOrder.php b/src/Component/Serializer/Annotation/AccessorOrder.php new file mode 100644 index 0000000..e8e7e09 --- /dev/null +++ b/src/Component/Serializer/Annotation/AccessorOrder.php @@ -0,0 +1,28 @@ + + * @internal + */ +final class AccessorOrder +{ + /** + * @Required + * @var string + */ + public $order; + + /** + * @var array + */ + public $custom = []; +} diff --git a/src/Component/Serializer/Annotation/Discriminator.php b/src/Component/Serializer/Annotation/Discriminator.php new file mode 100644 index 0000000..1bdd7ef --- /dev/null +++ b/src/Component/Serializer/Annotation/Discriminator.php @@ -0,0 +1,26 @@ + + * @internal + */ +class Discriminator +{ + /** @var array */ + public $map; + + /** @var string */ + public $field = 'type'; + + /** @var bool */ + public $disabled = false; + + /** @var string[] */ + public $groups = []; +} diff --git a/src/Component/Serializer/Annotation/Exclude.php b/src/Component/Serializer/Annotation/Exclude.php new file mode 100644 index 0000000..c6ea631 --- /dev/null +++ b/src/Component/Serializer/Annotation/Exclude.php @@ -0,0 +1,19 @@ + + * @internal + */ +final class Exclude +{ + /** + * @var string + */ + public $if; +} diff --git a/src/Component/Serializer/Annotation/ExclusionPolicy.php b/src/Component/Serializer/Annotation/ExclusionPolicy.php new file mode 100644 index 0000000..e32f898 --- /dev/null +++ b/src/Component/Serializer/Annotation/ExclusionPolicy.php @@ -0,0 +1,54 @@ + + * @internal + */ +final class ExclusionPolicy +{ + public const NONE = 'NONE'; + public const ALL = 'ALL'; + + /** + * @var string + */ + public $policy; + + /** + * ExclusionPolicy constructor. + * + * @param array $values + */ + public function __construct(array $values) + { + $value = self::NONE; + + if (array_key_exists('value', $values)) { + $value = $values['value']; + } + + if (array_key_exists('policy', $values)) { + $value = $values['policy']; + } + + if (!\is_string($value)) { + throw new RuntimeException('Exclusion policy value must be of string type.'); + } + + $value = strtoupper($value); + + if (self::NONE !== $value && self::ALL !== $value) { + throw new RuntimeException('Exclusion policy must either be "ALL", or "NONE".'); + } + + $this->policy = $value; + } +} diff --git a/src/Component/Serializer/Annotation/Expose.php b/src/Component/Serializer/Annotation/Expose.php new file mode 100644 index 0000000..6887817 --- /dev/null +++ b/src/Component/Serializer/Annotation/Expose.php @@ -0,0 +1,19 @@ + + * @internal + */ +final class Expose +{ + /** + * @var string + */ + public $if; +} diff --git a/src/Component/Serializer/Annotation/Groups.php b/src/Component/Serializer/Annotation/Groups.php new file mode 100644 index 0000000..b58ef4e --- /dev/null +++ b/src/Component/Serializer/Annotation/Groups.php @@ -0,0 +1,17 @@ + + * @internal + */ +final class Groups +{ + /** @var array @Required */ + public $groups; +} diff --git a/src/Component/Serializer/Annotation/Inline.php b/src/Component/Serializer/Annotation/Inline.php new file mode 100644 index 0000000..9c4c795 --- /dev/null +++ b/src/Component/Serializer/Annotation/Inline.php @@ -0,0 +1,15 @@ + + * @internal + */ +final class Inline +{ +} diff --git a/src/Component/Serializer/Annotation/MaxDepth.php b/src/Component/Serializer/Annotation/MaxDepth.php new file mode 100644 index 0000000..edd8071 --- /dev/null +++ b/src/Component/Serializer/Annotation/MaxDepth.php @@ -0,0 +1,20 @@ + + * @internal + */ +final class MaxDepth +{ + /** + * @Required + * @var int + */ + public $depth; +} diff --git a/src/Component/Serializer/Annotation/PostDeserialize.php b/src/Component/Serializer/Annotation/PostDeserialize.php new file mode 100644 index 0000000..e8af4c7 --- /dev/null +++ b/src/Component/Serializer/Annotation/PostDeserialize.php @@ -0,0 +1,21 @@ + + * @internal + */ +final class PostDeserialize +{ +} diff --git a/src/Component/Serializer/Annotation/PostSerialize.php b/src/Component/Serializer/Annotation/PostSerialize.php new file mode 100644 index 0000000..1c070f1 --- /dev/null +++ b/src/Component/Serializer/Annotation/PostSerialize.php @@ -0,0 +1,15 @@ + + * @internal + */ +final class PostSerialize +{ +} diff --git a/src/Component/Serializer/Annotation/PreSerialize.php b/src/Component/Serializer/Annotation/PreSerialize.php new file mode 100644 index 0000000..b7b16e2 --- /dev/null +++ b/src/Component/Serializer/Annotation/PreSerialize.php @@ -0,0 +1,22 @@ + + * @internal + */ +final class PreSerialize +{ +} diff --git a/src/Component/Serializer/Annotation/ReadOnly.php b/src/Component/Serializer/Annotation/ReadOnly.php new file mode 100644 index 0000000..ae84b9a --- /dev/null +++ b/src/Component/Serializer/Annotation/ReadOnly.php @@ -0,0 +1,19 @@ + + * @internal + */ +final class ReadOnly +{ + /** + * @var bool + */ + public $readOnly = true; +} diff --git a/src/Component/Serializer/Annotation/SerializedName.php b/src/Component/Serializer/Annotation/SerializedName.php new file mode 100644 index 0000000..3c90114 --- /dev/null +++ b/src/Component/Serializer/Annotation/SerializedName.php @@ -0,0 +1,35 @@ + + * @internal + */ +final class SerializedName +{ + /** + * @var string + */ + public $name; + + /** + * SerializedName constructor. + * + * @param array $values + */ + public function __construct(array $values) + { + if (!isset($values['value']) || !\is_string($values['value'])) { + throw new RuntimeException(sprintf('"value" must be a string.')); + } + + $this->name = $values['value']; + } +} diff --git a/src/Component/Serializer/Annotation/Since.php b/src/Component/Serializer/Annotation/Since.php new file mode 100644 index 0000000..84e3bde --- /dev/null +++ b/src/Component/Serializer/Annotation/Since.php @@ -0,0 +1,15 @@ + + * @internal + */ +final class Since extends Version +{ +} diff --git a/src/Component/Serializer/Annotation/SkipWhenEmpty.php b/src/Component/Serializer/Annotation/SkipWhenEmpty.php new file mode 100644 index 0000000..8d395df --- /dev/null +++ b/src/Component/Serializer/Annotation/SkipWhenEmpty.php @@ -0,0 +1,15 @@ + + * @internal + */ +final class SkipWhenEmpty +{ +} diff --git a/src/Component/Serializer/Annotation/Type.php b/src/Component/Serializer/Annotation/Type.php new file mode 100644 index 0000000..4797f9e --- /dev/null +++ b/src/Component/Serializer/Annotation/Type.php @@ -0,0 +1,20 @@ + + * @internal + */ +final class Type +{ + /** + * @Required + * @var string + */ + public $name; +} diff --git a/src/Component/Serializer/Annotation/Until.php b/src/Component/Serializer/Annotation/Until.php new file mode 100644 index 0000000..51e044a --- /dev/null +++ b/src/Component/Serializer/Annotation/Until.php @@ -0,0 +1,15 @@ + + * @internal + */ +final class Until extends Version +{ +} diff --git a/src/Component/Serializer/Annotation/Version.php b/src/Component/Serializer/Annotation/Version.php new file mode 100644 index 0000000..7730e1e --- /dev/null +++ b/src/Component/Serializer/Annotation/Version.php @@ -0,0 +1,21 @@ + + * @internal + */ +abstract class Version +{ + /** + * @Required + * @var string + */ + public $version; +} diff --git a/src/Component/Serializer/Annotation/VirtualProperty.php b/src/Component/Serializer/Annotation/VirtualProperty.php new file mode 100644 index 0000000..d769217 --- /dev/null +++ b/src/Component/Serializer/Annotation/VirtualProperty.php @@ -0,0 +1,57 @@ + + * @internal + */ +final class VirtualProperty +{ + /** + * @var string + */ + public $exp; + + /** + * @var string + */ + public $name; + + /** + * @var array + */ + public $options = []; + + /** + * VirtualProperty constructor. + * + * @param array $data + */ + public function __construct(array $data) + { + if (isset($data['value'])) { + $data['name'] = $data['value']; + unset($data['value']); + } + + foreach ($data as $key => $value) { + if (!property_exists(self::class, $key)) { + throw new InvalidArgumentException(sprintf( + 'Unknown property "%s" on annotation "%s".', + $key, + self::class + )); + } + + $this->{$key} = $value; + } + } +} diff --git a/src/Component/Serializer/ArraySupportDecorator.php b/src/Component/Serializer/ArraySupportDecorator.php new file mode 100644 index 0000000..0e97d0e --- /dev/null +++ b/src/Component/Serializer/ArraySupportDecorator.php @@ -0,0 +1,227 @@ +serializer = $serializer; + } + + /** + * @inheritDoc + * @throws \JsonException + */ + public function serialize($data, string $format, ?Context $context = null): string + { + if ('json' !== $format) { + throw new UnsupportedFormatException('Liip serializer only supports JSON for now'); + } + + if (is_array($data)) { + try { + return Json::encode($this->encodeArray($data, $context), JSON_UNESCAPED_SLASHES); + } catch (JsonException $exception) { + throw new Exception( + sprintf( + 'Failed to JSON encode data for %s. This is not supposed to happen.', + // @phpstan-ignore-next-line + is_object($data) ? get_class($data) : gettype($data) + ), + 0, + $exception + ); + } + } + + return $this->serializer->serialize($data, $format, $context); + } + + /** + * @inheritDoc + */ + public function deserialize(string $data, string $type, string $format, ?Context $context = null) + { + if ('json' !== $format) { + throw new UnsupportedFormatException('Liip serializer only supports JSON for now'); + } + + if (static::isArrayType($type)) { + try { + $array = Json::decode($data, true); + } catch (JsonException $exception) { + throw new Exception('Failed to JSON decode data. This is not supposed to happen.', 0, $exception); + } + + return $this->serializer->fromArray($this->decodeArray($array, $type, $context), $type, $context); + } + + return $this->serializer->deserialize($data, $type, $format, $context); + } + + /** + * @inheritDoc + * + * @return array + */ + public function toArray($data, ?Context $context = null): array + { + if (is_array($data)) { + return $this->encodeArray($data, $context); + } + + return $this->serializer->toArray($data, $context); + } + + /** + * @inheritDoc + * + * @param array $data + * + * @return array|object + */ + public function fromArray(array $data, string $type, ?Context $context = null) + { + if (static::isArrayType($type)) { + return $this->decodeArray($data, $type, $context); + } + + return $this->serializer->fromArray($data, $type, $context); + } + + /** + * Encodes array of objects into simple multidimensional array. + * + * @param mixed[] $data + * @param \Liip\Serializer\Context|null $context + * + * @return mixed[] + * @throws \Liip\Serializer\Exception\Exception + * @throws \Liip\Serializer\Exception\UnsupportedTypeException + */ + private function encodeArray(array $data, ?Context $context = null): array + { + $result = []; + + foreach ($data as $key => $value) { + switch (gettype($value)) { + case 'array': + $result[$key] = $this->encodeArray($value, $context); + break; + case 'object': + $result[$key] = $this->serializer->toArray($value, $context); + break; + default: + $result[$key] = $value; + break; + } + } + + return $result; + } + + /** + * Decodes array of arrays to array of objects. + * + * @param mixed[] $data + * @param string $type + * @param \Liip\Serializer\Context|null $context + * + * @return array + * @throws \Liip\Serializer\Exception\Exception + * @throws \Liip\Serializer\Exception\UnsupportedTypeException + */ + private function decodeArray(array $data, string $type, ?Context $context = null): array + { + $result = []; + $subtype = static::getArrayValueType($type); + + if (class_exists($subtype)) { + foreach ($data as $key => $item) { + if (is_array($item)) { + $result[$key] = $this->decodeArray($item, $subtype, $context); + continue; + } + + $result[$key] = $item; + } + + return $result; + } + + return $data; + } + + /** + * Returns true if provided type is an array. + * + * @param string $type + * + * @return bool + */ + private static function isArrayType(string $type): bool + { + return false !== strpos($type, 'array'); + } + + /** + * Returns array value type from types like 'array' or 'array'. + * + * @param string $type + * + * @return string + */ + private static function getArrayValueType(string $type): string + { + $matches = []; + + preg_match_all( + '/array(\s+)?\<([\w\|\\\\]+)\s+\,\s+([\w\|\\\\]+)\>/m', + $type, + $matches, + PREG_SET_ORDER, + 0 + ); + + if (count($matches) > 0) { + return $matches[count($matches) - 1]; + } + + preg_match_all('/array(\s+)?\<([\w\|\\\\]+)\>/m', $type, $matches, PREG_SET_ORDER, 0); + + if (count($matches) > 0) { + return $matches[count($matches) - 1]; + } + + return 'mixed'; + } +} diff --git a/src/Component/Serializer/Exception/InvalidArgumentException.php b/src/Component/Serializer/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..5f07b96 --- /dev/null +++ b/src/Component/Serializer/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * @internal + */ +class InvalidArgumentException extends BaseException +{ +} diff --git a/src/Component/Serializer/Exception/InvalidNode.php b/src/Component/Serializer/Exception/InvalidNode.php new file mode 100644 index 0000000..70dda0a --- /dev/null +++ b/src/Component/Serializer/Exception/InvalidNode.php @@ -0,0 +1,24 @@ + + * @internal + */ +class InvalidNode extends LogicException +{ +} diff --git a/src/Component/Serializer/Exception/RuntimeException.php b/src/Component/Serializer/Exception/RuntimeException.php new file mode 100644 index 0000000..16af2d6 --- /dev/null +++ b/src/Component/Serializer/Exception/RuntimeException.php @@ -0,0 +1,27 @@ + + * @internal + */ +class RuntimeException extends BaseException +{ + public static function noMetadataForProperty(string $class, string $prop): self + { + return new self(sprintf( + 'You must define a type for %s::$%s.', + $class, + $prop + )); + } +} diff --git a/src/Component/Serializer/Exception/SyntaxError.php b/src/Component/Serializer/Exception/SyntaxError.php new file mode 100644 index 0000000..7892450 --- /dev/null +++ b/src/Component/Serializer/Exception/SyntaxError.php @@ -0,0 +1,24 @@ + + * @internal + */ +class SyntaxError extends RuntimeException +{ +} diff --git a/src/Component/Serializer/Generator/DeserializerGenerator.php b/src/Component/Serializer/Generator/DeserializerGenerator.php new file mode 100644 index 0000000..eb46c7d --- /dev/null +++ b/src/Component/Serializer/Generator/DeserializerGenerator.php @@ -0,0 +1,463 @@ + + * @author Pavel Kovalenko + * @see https://github.com/liip/serializer + * @internal + * + * @SuppressWarnings(PHPMD) + */ +class DeserializerGenerator +{ + private const FILENAME_PREFIX = 'deserialize'; + + /** + * @var Deserialization + */ + private $templating; + + /** + * @var \RetailCrm\Api\Component\Serializer\Template\CustomDeserialization + */ + private $customTemplating; + + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @var Builder + */ + private $metadataBuilder; + + /** + * This is a list of fqn classnames + * + * I.e. + * + * [ + * Product::class, + * ]; + * + * @var string[] + */ + private $classesToGenerate; + + /** + * @var string + */ + private $cacheDirectory; + + /** + * @param \Liip\Serializer\Template\Deserialization $templating + * @param \RetailCrm\Api\Component\Serializer\Template\CustomDeserialization $customTemplating + * @param string[] $classesToGenerate + * @param string $cacheDirectory + */ + public function __construct( + Deserialization $templating, + CustomDeserialization $customTemplating, + array $classesToGenerate, + string $cacheDirectory + ) { + $this->templating = $templating; + $this->customTemplating = $customTemplating; + $this->classesToGenerate = $classesToGenerate; + $this->cacheDirectory = $cacheDirectory; + $this->filesystem = new Filesystem(); + } + + /** + * @param string $className + * + * @return string + */ + public static function buildDeserializerFunctionName(string $className): string + { + return static::FILENAME_PREFIX . '_' . str_replace('\\', '_', $className); + } + + /** + * @param \Liip\MetadataParser\Builder $metadataBuilder + * + * @throws \Exception + */ + public function generate(Builder $metadataBuilder): void + { + $this->metadataBuilder = $metadataBuilder; + + $this->filesystem->mkdir($this->cacheDirectory); + + foreach ($this->classesToGenerate as $className) { + // we do not use the oldest version reducer here and hope for the best + // otherwise we end up with generated property names for accessor methods + $classMetadata = $metadataBuilder->build($className, [ + new TakeBestReducer(), + ]); + $this->writeFile($classMetadata); + } + } + + /** + * @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata + * + * @throws \Exception + */ + private function writeFile(ClassMetadata $classMetadata): void + { + if (count($classMetadata->getConstructorParameters())) { + throw new RuntimeException(sprintf( + 'We currently do not support deserializing when the root class has a non-empty constructor. Class %s', + $classMetadata->getClassName() + )); + } + + $functionName = static::buildDeserializerFunctionName($classMetadata->getClassName()); + $arrayPath = new ArrayPath('jsonData'); + + $code = $this->templating->renderFunction( + $functionName, + $classMetadata->getClassName(), + (string) $arrayPath, + $this->generateCodeForClass($classMetadata, $arrayPath, new ModelPath('model')) + ); + + $this->filesystem->dumpFile(sprintf('%s/%s.php', $this->cacheDirectory, $functionName), $code); + } + + /** + * @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata + * @param \Liip\Serializer\Path\ArrayPath $arrayPath + * @param \Liip\Serializer\Path\ModelPath $modelPath + * @param mixed[] $stack + * + * @return string + * @throws \Exception + */ + private function generateCodeForClass( + ClassMetadata $classMetadata, + ArrayPath $arrayPath, + ModelPath $modelPath, + array $stack = [] + ): string { + $stack[$classMetadata->getClassName()] = ($stack[$classMetadata->getClassName()] ?? 0) + 1; + + $constructorArgumentNames = []; + $initCode = ''; + $code = ''; + + foreach ($classMetadata->getProperties() as $propertyMetadata) { + $propertyArrayPath = $arrayPath->withFieldName($propertyMetadata->getSerializedName()); + + if ($classMetadata->hasConstructorParameter($propertyMetadata->getName())) { + $argument = $classMetadata->getConstructorParameter($propertyMetadata->getName()); + $default = var_export($argument->isRequired() ? null : $argument->getDefaultValue(), true); + $tempVariable = ModelPath::tempVariable([(string) $modelPath, $propertyMetadata->getName()]); + $constructorArgumentNames[$propertyMetadata->getName()] = (string) $tempVariable; + + $initCode .= $this->templating->renderArgument( + (string) $tempVariable, + $default, + $this->generateCodeForField($propertyMetadata, $propertyArrayPath, $tempVariable, $stack) + ); + } else { + $code .= $this->generateCodeForProperty($propertyMetadata, $propertyArrayPath, $modelPath, $stack); + } + } + + foreach ($classMetadata->getPostDeserializeMethods() as $method) { + $code .= $this->templating->renderPostMethod((string) $modelPath, $method); + } + + $constructorArguments = []; + + foreach ($classMetadata->getConstructorParameters() as $definition) { + if (array_key_exists($definition->getName(), $constructorArgumentNames)) { + $constructorArguments[] = $constructorArgumentNames[$definition->getName()]; + continue; + } + + if ($definition->isRequired()) { + throw new RuntimeException(sprintf( + 'Unknown constructor argument "%s" in "%s(%s)"', + $definition->getName(), + $classMetadata->getClassName(), + implode(', ', array_keys($constructorArgumentNames)) + )); + } + + $constructorArguments[] = var_export($definition->getDefaultValue(), true); + } + + if (count($constructorArgumentNames) > 0) { + $code .= $this->templating->renderUnset(array_values($constructorArgumentNames)); + } + + if (CustomerInterface::class === $classMetadata->getClassName()) { + return $this->generateCustomerInterface($classMetadata, $arrayPath, $modelPath, $initCode, $stack); + } + + return $this->templating->renderClass( + (string) $modelPath, + $classMetadata->getClassName(), + $constructorArguments, + $code, + $initCode + ); + } + + /** + * @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata + * @param \Liip\Serializer\Path\ArrayPath $arrayPath + * @param \Liip\Serializer\Path\ModelPath $modelPath + * @param string $initCode + * @param array $stack + * + * @return string + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\SyntaxError + */ + private function generateCustomerInterface( + ClassMetadata $classMetadata, + ArrayPath $arrayPath, + ModelPath $modelPath, + string $initCode, + array $stack = [] + ): string { + $customerMetadata = $this->metadataBuilder->build(Customer::class, [ + new TakeBestReducer(), + ]); + $corporateMetadata = $this->metadataBuilder->build(CustomerCorporate::class, [ + new TakeBestReducer(), + ]); + + $customerCode = $this->generateCodeForClass($customerMetadata, $arrayPath, $modelPath, $stack); + $corporateCode = $this->generateCodeForClass($corporateMetadata, $arrayPath, $modelPath, $stack); + + return $this->customTemplating->renderCustomerInterface( + (string) $arrayPath, + (string) $modelPath, + $classMetadata->getClassName(), + [], + $customerCode, + $corporateCode, + $initCode + ); + } + + /** + * @param \Liip\MetadataParser\Metadata\PropertyMetadata $propertyMetadata + * @param \Liip\Serializer\Path\ArrayPath $arrayPath + * @param \Liip\Serializer\Path\ModelPath $modelPath + * @param mixed[] $stack + * + * @return string + * @throws \Exception + */ + private function generateCodeForProperty( + PropertyMetadata $propertyMetadata, + ArrayPath $arrayPath, + ModelPath $modelPath, + array $stack + ): string { + if ($propertyMetadata->isReadOnly()) { + return ''; + } + + if ($propertyMetadata->getAccessor()->hasSetterMethod()) { + $tempVariable = ModelPath::tempVariable([(string) $modelPath, $propertyMetadata->getName()]); + $code = $this->generateCodeForField($propertyMetadata, $arrayPath, $tempVariable, $stack); + $code .= $this->templating->renderConditional( + (string) $tempVariable, + $this->templating->renderSetter( + (string) $modelPath, + (string) $propertyMetadata->getAccessor()->getSetterMethod(), + (string) $tempVariable + ) + ); + $code .= $this->templating->renderUnset([(string) $tempVariable]); + + return $code; + } + + $modelPropertyPath = $modelPath->withPath($propertyMetadata->getName()); + + return $this->generateCodeForField($propertyMetadata, $arrayPath, $modelPropertyPath, $stack); + } + + /** + * @param \Liip\MetadataParser\Metadata\PropertyMetadata $propertyMetadata + * @param \Liip\Serializer\Path\ArrayPath $arrayPath + * @param \Liip\Serializer\Path\ModelPath $modelPath + * @param mixed[] $stack + * + * @return string + * @throws \Exception + */ + private function generateCodeForField( + PropertyMetadata $propertyMetadata, + ArrayPath $arrayPath, + ModelPath $modelPath, + array $stack + ): string { + return $this->templating->renderConditional( + (string) $arrayPath, + $this->generateInnerCodeForFieldType($propertyMetadata, $arrayPath, $modelPath, $stack) + ); + } + + /** + * @param \Liip\MetadataParser\Metadata\PropertyMetadata $propertyMetadata + * @param \Liip\Serializer\Path\ArrayPath $arrayPath + * @param \Liip\Serializer\Path\ModelPath $modelPropertyPath + * @param mixed[] $stack + * + * @return string + * @throws \Exception + */ + private function generateInnerCodeForFieldType( + PropertyMetadata $propertyMetadata, + ArrayPath $arrayPath, + ModelPath $modelPropertyPath, + array $stack + ): string { + $type = $propertyMetadata->getType(); + + if ($type instanceof PropertyTypeArray) { + if ($type->getSubType() instanceof PropertyTypePrimitive) { + // for arrays of scalars, copy the field even when its an empty array + return $this->templating->renderAssignJsonDataToField((string) $modelPropertyPath, (string) $arrayPath); + } + + // either array or hashmap with second param the type of values + // the index works the same whether its numeric or hashmap + return $this->generateCodeForArray($type, $arrayPath, $modelPropertyPath, $stack); + } + + switch ($type) { + case $type instanceof PropertyTypeDateTime: + if (null !== $type->getZone()) { + throw new RuntimeException('Timezone support is not implemented'); + } + + $format = $type->getDeserializeFormat() ?: $type->getFormat(); + + if (null !== $format) { + return $this->templating->renderAssignDateTimeFromFormat( + $type->isImmutable(), + (string) $modelPropertyPath, + (string) $arrayPath, + $format + ); + } + + return $this->templating->renderAssignDateTimeToField( + $type->isImmutable(), + (string) $modelPropertyPath, + (string) $arrayPath + ); + case $type instanceof PropertyTypePrimitive && 'float' === $type->getTypeName(): + return $this->templating->renderAssignJsonDataToFieldWithCasting( + (string) $modelPropertyPath, + (string) $arrayPath, + 'float' + ); + case $type instanceof PropertyTypePrimitive: + case $type instanceof PropertyTypeUnknown: + case $type instanceof PropertyTypeMixed: + return $this->templating->renderAssignJsonDataToField((string) $modelPropertyPath, (string) $arrayPath); + case $type instanceof PropertyTypeClass: + return $this->generateCodeForClass($type->getClassMetadata(), $arrayPath, $modelPropertyPath, $stack); + default: + throw new RuntimeException('Unexpected type ' . get_class($type) . ' at ' . $modelPropertyPath); + } + } + + /** + * @param \Liip\MetadataParser\Metadata\PropertyTypeArray $type + * @param \Liip\Serializer\Path\ArrayPath $arrayPath + * @param \Liip\Serializer\Path\ModelPath $modelPath + * @param mixed[] $stack + * + * @return string + * @throws \Exception + */ + private function generateCodeForArray( + PropertyTypeArray $type, + ArrayPath $arrayPath, + ModelPath $modelPath, + array $stack + ): string { + $index = ModelPath::indexVariable((string) $arrayPath); + $arrayPropertyPath = $arrayPath->withVariable((string) $index); + $modelPropertyPath = $modelPath->withArray((string) $index); + $subType = $type->getSubType(); + + switch ($subType) { + case $subType instanceof PropertyTypeArray: + $innerCode = $this->generateCodeForArray($subType, $arrayPropertyPath, $modelPropertyPath, $stack); + break; + case $subType instanceof PropertyTypeClass: + $innerCode = $this->generateCodeForClass( + $subType->getClassMetadata(), + $arrayPropertyPath, + $modelPropertyPath, + $stack + ); + break; + case $subType instanceof PropertyTypeUnknown: + $innerCode = $this->templating->renderAssignJsonDataToField( + $modelPropertyPath, + $arrayPropertyPath + ); + break; + default: + throw new RuntimeException('Unexpected array subtype ' . get_class($subType)); + } + + if ('' === $innerCode) { + return ''; + } + + $code = $this->templating->renderInitArray((string) $modelPath); + $code .= $this->templating->renderLoop((string) $arrayPath, (string) $index, $innerCode); + + return $code; + } +} diff --git a/src/Component/Serializer/Generator/SerializerGenerator.php b/src/Component/Serializer/Generator/SerializerGenerator.php new file mode 100644 index 0000000..0083a1d --- /dev/null +++ b/src/Component/Serializer/Generator/SerializerGenerator.php @@ -0,0 +1,498 @@ + + * @author Pavel Kovalenko + * @see https://github.com/liip/serializer + * @internal + * + * @SuppressWarnings(PHPMD) + */ +class SerializerGenerator +{ + private const FILENAME_PREFIX = 'serialize'; + + /** + * @var Serialization + */ + private $templating; + + /** + * @var \RetailCrm\Api\Component\Serializer\Template\CustomSerialization + */ + private $customTemplating; + + /** + * @var Builder + */ + private $metadataBuilder; + + /** + * @var GeneratorConfiguration + */ + private $configuration; + + /** + * @var string + */ + private $cacheDirectory; + + /** + * @var Filesystem + */ + private $filesystem; + + /** + * SerializerGenerator constructor. + * + * @param \Liip\Serializer\Template\Serialization $templating + * @param \RetailCrm\Api\Component\Serializer\Template\CustomSerialization $customTemplating + * @param \Liip\Serializer\Configuration\GeneratorConfiguration $configuration + * @param string $cacheDirectory + */ + public function __construct( + Serialization $templating, + CustomSerialization $customTemplating, + GeneratorConfiguration $configuration, + string $cacheDirectory + ) { + $this->templating = $templating; + $this->customTemplating = $customTemplating; + $this->configuration = $configuration; + $this->cacheDirectory = $cacheDirectory; + + $this->filesystem = new Filesystem(); + } + + /** + * @param string $className + * @param string|null $apiVersion + * @param array $serializerGroups + * + * @return string + */ + public static function buildSerializerFunctionName( + string $className, + ?string $apiVersion, + array $serializerGroups + ): string { + $functionName = static::FILENAME_PREFIX . '_' . $className; + + if (count($serializerGroups)) { + $functionName .= '_' . implode('_', $serializerGroups); + } + + if (null !== $apiVersion) { + $functionName .= '_' . $apiVersion; + } + + return (string) preg_replace('/[^a-zA-Z0-9_]/', '_', $functionName); + } + + /** + * @param \Liip\MetadataParser\Builder $metadataBuilder + */ + public function generate(Builder $metadataBuilder): void + { + $this->metadataBuilder = $metadataBuilder; + + $this->filesystem->mkdir($this->cacheDirectory); + + foreach ($this->configuration as $classToGenerate) { + foreach ($classToGenerate as $groupCombination) { + $className = $classToGenerate->getClassName(); + + foreach ($groupCombination->getVersions() as $version) { + if ('' === $version) { + $metadata = $metadataBuilder->build($className, [ + new PreferredReducer(), + new TakeBestReducer(), + ]); + + $this->writeFile($className, null, $groupCombination->getGroups(), $metadata); + } else { + $metadata = $metadataBuilder->build($className, [ + new VersionReducer($version), + new GroupReducer($groupCombination->getGroups()), + new TakeBestReducer(), + ]); + + $this->writeFile($className, $version, $groupCombination->getGroups(), $metadata); + } + } + } + } + } + + /** + * @param string $className + * @param string|null $apiVersion + * @param array $serializerGroups + * @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata + */ + private function writeFile( + string $className, + ?string $apiVersion, + array $serializerGroups, + ClassMetadata $classMetadata + ): void { + sort($serializerGroups); + $functionName = static::buildSerializerFunctionName($className, $apiVersion, $serializerGroups); + + $code = $this->templating->renderFunction( + $functionName, + $className, + $this->generateCodeForClass($classMetadata, $apiVersion, $serializerGroups, '', '$model') + ); + + $this->filesystem->dumpFile(sprintf('%s/%s.php', $this->cacheDirectory, $functionName), $code); + } + + /** + * @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata + * @param string|null $apiVersion + * @param array $serializerGroups + * @param string $arrayPath + * @param string $modelPath + * @param array $stack + * + * @return string + * @throws \Exception + */ + private function generateCodeForClass( + ClassMetadata $classMetadata, + ?string $apiVersion, + array $serializerGroups, + string $arrayPath, + string $modelPath, + array $stack = [] + ): string { + if ($classMetadata->getClassName() === CustomerInterface::class) { + return $this->generateForCustomerInterface( + $classMetadata, + $apiVersion, + $serializerGroups, + $arrayPath, + $modelPath, + $stack + ); + } + + if ($classMetadata->getClassName() === CustomerTag::class) { + return $this->generateForCustomerTag($arrayPath, $modelPath); + } + + $stack[$classMetadata->getClassName()] = ($stack[$classMetadata->getClassName()] ?? 0) + 1; + $code = ''; + + foreach ($classMetadata->getProperties() as $propertyMetadata) { + $code .= $this->generateCodeForField( + $propertyMetadata, + $apiVersion, + $serializerGroups, + $arrayPath, + $modelPath, + $stack + ); + } + + return $this->templating->renderClass($arrayPath, $code); + } + + /** + * @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata + * @param string|null $apiVersion + * @param array $serializerGroups + * @param string $arrayPath + * @param string $modelPath + * @param array $stack + * + * @return string + * @throws \Exception + */ + private function generateForCustomerInterface( + ClassMetadata $classMetadata, + ?string $apiVersion, + array $serializerGroups, + string $arrayPath, + string $modelPath, + array $stack = [] + ): string { + $customerMetadata = $this->metadataBuilder->build(Customer::class, [ + new PreferredReducer(), + new TakeBestReducer(), + ]); + $corporateMetadata = $this->metadataBuilder->build(CustomerCorporate::class, [ + new PreferredReducer(), + new TakeBestReducer(), + ]); + $customerCode = $this->generateCodeForClass( + $customerMetadata, + $apiVersion, + $serializerGroups, + $arrayPath, + $modelPath + ); + $corporateCode = $this->generateCodeForClass( + $corporateMetadata, + $apiVersion, + $serializerGroups, + $arrayPath, + $modelPath + ); + + return $this->customTemplating->renderCustomerInterface($arrayPath, $modelPath, $customerCode, $corporateCode); + } + + /** + * @param string $arrayPath + * @param string $modelPath + * + * @return string + */ + private function generateForCustomerTag(string $arrayPath, string $modelPath): string + { + return $this->templating->renderAssign($arrayPath, $modelPath . '->name'); + } + + /** + * @param \Liip\MetadataParser\Metadata\PropertyMetadata $propertyMetadata + * @param string|null $apiVersion + * @param array $serializerGroups + * @param string $arrayPath + * @param string $modelPath + * @param array $stack + * + * @return string + * @throws \Exception + */ + private function generateCodeForField( + PropertyMetadata $propertyMetadata, + ?string $apiVersion, + array $serializerGroups, + string $arrayPath, + string $modelPath, + array $stack + ): string { + $modelPropertyPath = $modelPath . '->' . $propertyMetadata->getName(); + $fieldPath = $arrayPath . '["' . $propertyMetadata->getSerializedName() . '"]'; + + if ($propertyMetadata->getAccessor()->hasGetterMethod()) { + $tempVariable = str_replace(['->', '[', ']', '$'], '', $modelPath) . ucfirst($propertyMetadata->getName()); + + return $this->templating->renderConditional( + $this->templating->renderTempVariable( + $tempVariable, + $this->templating->renderGetter( + $modelPath, + (string) $propertyMetadata->getAccessor()->getGetterMethod() + ) + ), + $this->generateCodeForFieldType( + $propertyMetadata->getType(), + $apiVersion, + $serializerGroups, + $fieldPath, + '$' . $tempVariable, + $stack + ) + ); + } + if (!$propertyMetadata->isPublic()) { + throw new RuntimeException(sprintf( + 'Property %s is not public and no getter has been defined. Stack %s', + $modelPropertyPath, + var_export($stack, true) + )); + } + + return $this->templating->renderConditional( + $modelPropertyPath, + $this->generateCodeForFieldType( + $propertyMetadata->getType(), + $apiVersion, + $serializerGroups, + $fieldPath, + $modelPropertyPath, + $stack + ) + ); + } + + /** + * @param \Liip\MetadataParser\Metadata\PropertyType $type + * @param string|null $apiVersion + * @param array $serializerGroups + * @param string $fieldPath + * @param string $modelPropertyPath + * @param array $stack + * + * @return string + * @throws \Exception + */ + private function generateCodeForFieldType( + PropertyType $type, + ?string $apiVersion, + array $serializerGroups, + string $fieldPath, + string $modelPropertyPath, + array $stack + ): string { + if ($type instanceof PropertyTypeArray) { + if ($type->getSubType() instanceof PropertyTypePrimitive) { + // for arrays of scalars, copy the field even when its an empty array + return $this->templating->renderAssign($fieldPath, $modelPropertyPath); + } + + // either array or hashmap with second param the type of values + // the index works the same whether its numeric or hashmap + return $this->generateCodeForArray( + $type, + $apiVersion, + $serializerGroups, + $fieldPath, + $modelPropertyPath, + $stack + ); + } + + switch ($type) { + case $type instanceof PropertyTypeDateTime: + if (null !== $type->getZone()) { + throw new \RuntimeException('Timezone support is not implemented'); + } + + $dateFormat = $type->getFormat() ?: DateTime::ATOM; + + return $this->templating->renderAssign( + $fieldPath, + $this->templating->renderDateTime($modelPropertyPath, $dateFormat) + ); + + case $type instanceof PropertyTypePrimitive: + case $type instanceof PropertyTypeUnknown: + case $type instanceof PropertyTypeMixed: + // for arrays of scalars, copy the field even when its an empty array + return $this->templating->renderAssign($fieldPath, $modelPropertyPath); + + case $type instanceof PropertyTypeClass: + return $this->generateCodeForClass( + $type->getClassMetadata(), + $apiVersion, + $serializerGroups, + $fieldPath, + $modelPropertyPath, + $stack + ); + + default: + throw new RuntimeException('Unexpected type ' . \get_class($type) . ' at ' . $modelPropertyPath); + } + } + + /** + * @param \Liip\MetadataParser\Metadata\PropertyTypeArray $type + * @param string|null $apiVersion + * @param array $serializerGroups + * @param string $arrayPath + * @param string $modelPath + * @param array $stack + * + * @return string + * @throws \Exception + */ + private function generateCodeForArray( + PropertyTypeArray $type, + ?string $apiVersion, + array $serializerGroups, + string $arrayPath, + string $modelPath, + array $stack + ): string { + $index = '$index' . \mb_strlen($arrayPath); + $subType = $type->getSubType(); + + switch ($subType) { + case $subType instanceof PropertyTypeArray: + $innerCode = $this->generateCodeForArray( + $subType, + $apiVersion, + $serializerGroups, + $arrayPath . '[' . $index . ']', + $modelPath . '[' . $index . ']', + $stack + ); + break; + case $subType instanceof PropertyTypeClass: + $innerCode = $this->generateCodeForClass( + $subType->getClassMetadata(), + $apiVersion, + $serializerGroups, + $arrayPath . '[' . $index . ']', + $modelPath . '[' . $index . ']', + $stack + ); + break; + case $subType instanceof PropertyTypeUnknown: + $innerCode = $this->templating->renderAssign($arrayPath, $modelPath); + break; + default: + throw new RuntimeException('Unexpected array subtype ' . get_class($subType)); + } + + if ('' === $innerCode) { + if ($type->isHashmap()) { + return $this->templating->renderLoopHashmapEmpty($arrayPath); + } + + return $this->templating->renderLoopArrayEmpty($arrayPath); + } + + if ($type->isHashmap()) { + return $this->templating->renderLoopHashmap($arrayPath, $modelPath, $index, $innerCode); + } + + return $this->templating->renderLoopArray($arrayPath, $modelPath, $index, $innerCode); + } +} diff --git a/src/Component/Serializer/ModelsChecksumGenerator.php b/src/Component/Serializer/ModelsChecksumGenerator.php new file mode 100644 index 0000000..55305e9 --- /dev/null +++ b/src/Component/Serializer/ModelsChecksumGenerator.php @@ -0,0 +1,116 @@ + $checsums + */ + public static function saveChecksums(array $checsums = []): void + { + try { + $data = json_encode($checsums, JSON_THROW_ON_ERROR); + } catch (JsonException $exception) { + $data = '{}'; + } + + if (false === file_put_contents(static::getChecksumFileName(), $data)) { + throw new RuntimeException('Cannot save checksums for models.'); + } + } + + /** + * Returns true if checksum is correct. Returns false otherwise. + * + * @return bool + */ + public static function verifyChecksum(): bool + { + $checksumFile = static::getChecksumFileName(); + + if (!file_exists($checksumFile)) { + return false; + } + + $oldHashes = static::getStoredChecksums(); + $newHashes = static::generateChecksums(); + + foreach ($newHashes as $fileName => $hash) { + if (!array_key_exists($fileName, $oldHashes)) { + return false; + } + + if ($hash !== $oldHashes[$fileName]) { + return false; + } + } + + return true; + } + + /** + * @return array + */ + public static function getStoredChecksums(): array + { + if (!is_file(static::getChecksumFileName())) { + return []; + } + + try { + return json_decode( + (string) file_get_contents(static::getChecksumFileName()), + true, + 512, + JSON_THROW_ON_ERROR + ); + } catch (JsonException $exception) { + return []; + } + } + + /** + * @return array + */ + public static function generateChecksums(): array + { + return (new FilesIteratorChecksumGenerator(new PhpFilesIterator(DevUtils::getModelsDirectory()))) + ->setFileNameAccessor(static function (array $item) { + return $item['file']; + })->setKeyTransformer(static function (array $item) { + return $item['fqn']; + })->generateHashes(); + } + + /** + * @return string + */ + private static function getChecksumFileName(): string + { + return implode(DIRECTORY_SEPARATOR, [Utils::getModelsCacheDirectory(), 'checksum.json']); + } +} diff --git a/src/Component/Serializer/Parser/BaseJMSParser.php b/src/Component/Serializer/Parser/BaseJMSParser.php new file mode 100644 index 0000000..3503208 --- /dev/null +++ b/src/Component/Serializer/Parser/BaseJMSParser.php @@ -0,0 +1,196 @@ + + * @internal + */ +final class BaseJMSParser +{ + /** + * @var \RetailCrm\Api\Component\Serializer\Parser\JMSLexer + */ + private $lexer; + + /** + * @var bool + */ + private $root = true; + + /** + * @param string $string + * + * @return array|mixed[] + */ + public function parse(string $string): array + { + $this->lexer = new JMSLexer(); + $this->lexer->setInput($string); + $this->lexer->moveNext(); + + return $this->visit(); + } + + /** + * @return mixed + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function visit() + { + $this->lexer->moveNext(); + + if (!$this->lexer->token) { + throw new SyntaxError( + 'Syntax error, unexpected end of stream' + ); + } + + if (JMSLexer::T_FLOAT === $this->lexer->token['type']) { + return (float)$this->lexer->token['value']; + } elseif (JMSLexer::T_INTEGER === $this->lexer->token['type']) { + return (int)$this->lexer->token['value']; + } elseif (JMSLexer::T_NULL === $this->lexer->token['type']) { + return null; + } elseif (JMSLexer::T_STRING === $this->lexer->token['type']) { + return $this->lexer->token['value']; + } elseif (JMSLexer::T_IDENTIFIER === $this->lexer->token['type']) { + if ($this->lexer->isNextToken(JMSLexer::T_TYPE_START)) { + return $this->visitCompoundType(); + } elseif ($this->lexer->isNextToken(JMSLexer::T_ARRAY_START)) { + return $this->visitArrayType(); + } + + return $this->visitSimpleType(); + } elseif (!$this->root && JMSLexer::T_ARRAY_START === $this->lexer->token['type']) { + return $this->visitArrayType(); + } + + throw new SyntaxError(sprintf( + 'Syntax error, unexpected "%s" (%s)', + $this->lexer->token['value'], + $this->getConstant($this->lexer->token['type']) + )); + } + + /** + * @return mixed[] + */ + private function visitSimpleType(): array + { + $value = $this->lexer->token['value']; // @phpstan-ignore-line + + return ['name' => $value, 'params' => []]; + } + + /** + * @return array + */ + private function visitCompoundType(): array + { + $this->root = false; + $name = $this->lexer->token['value']; // @phpstan-ignore-line + $this->match(JMSLexer::T_TYPE_START); + + $params = []; + if (!$this->lexer->isNextToken(JMSLexer::T_TYPE_END)) { + while (true) { + $params[] = $this->visit(); + + if ($this->lexer->isNextToken(JMSLexer::T_TYPE_END)) { + break; + } + + $this->match(JMSLexer::T_COMMA); + } + } + + $this->match(JMSLexer::T_TYPE_END); + + return [ + 'name' => $name, + 'params' => $params, + ]; + } + + /** + * @return array + */ + private function visitArrayType(): array + { + /* + * Here we should call $this->match(JMSLexer::T_ARRAY_START); to make it clean + * but the token has already been consumed by moveNext() in visit() + */ + $params = []; + + if (!$this->lexer->isNextToken(JMSLexer::T_ARRAY_END)) { + while (true) { + $params[] = $this->visit(); + + if ($this->lexer->isNextToken(JMSLexer::T_ARRAY_END)) { + break; + } + + $this->match(JMSLexer::T_COMMA); + } + } + + $this->match(JMSLexer::T_ARRAY_END); + + return $params; + } + + /** + * @param int $token + */ + private function match(int $token): void + { + if (!$this->lexer->lookahead) { + throw new SyntaxError( + sprintf('Syntax error, unexpected end of stream, expected %s', $this->getConstant($token)) + ); + } + + if ($this->lexer->lookahead['type'] === $token) { + $this->lexer->moveNext(); + + return; + } + + throw new SyntaxError(sprintf( + 'Syntax error, unexpected "%s" (%s), expected was %s', + $this->lexer->lookahead['value'], + $this->getConstant($this->lexer->lookahead['type']), + $this->getConstant($token) + )); + } + + /** + * @param int $value + * + * @return string + */ + private function getConstant(int $value): string + { + $oClass = new ReflectionClass(JMSLexer::class); + + return (string) array_search($value, $oClass->getConstants()); + } +} diff --git a/src/Component/Serializer/Parser/JMSLexer.php b/src/Component/Serializer/Parser/JMSLexer.php new file mode 100644 index 0000000..f2da682 --- /dev/null +++ b/src/Component/Serializer/Parser/JMSLexer.php @@ -0,0 +1,132 @@ +getType($type); + } catch (Throwable $e) { + throw new SyntaxError($e->getMessage(), 0, $e); + } + } + + /** + * @return string[] + */ + protected function getCatchablePatterns(): array + { + return [ + '[a-z][a-z_\\\\0-9]*', // identifier or qualified name + "'(?:[^']|'')*'", // single quoted strings + '(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', // numbers + '"(?:[^"]|"")*"', // double quoted strings + '<', + '>', + '\\[', + '\\]', + ]; + } + + /** + * @return string[] + */ + protected function getNonCatchablePatterns(): array + { + return ['\s+']; + } + + /** + * {{@inheritDoc}} + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + protected function getType(&$value) + { + $type = self::T_UNKNOWN; + + switch (true) { + // Recognize numeric values + case is_numeric($value): + if (false !== strpos($value, '.') || false !== stripos($value, 'e')) { + return self::T_FLOAT; + } + + return self::T_INTEGER; + + // Recognize quoted strings + case "'" === $value[0]: + $value = str_replace("''", "'", substr($value, 1, strlen($value) - 2)); + + return self::T_STRING; + + case '"' === $value[0]: + $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); + + return self::T_STRING; + + case 'null' === $value: + return self::T_NULL; + + // Recognize identifiers, aliased or qualified names + case ctype_alpha($value[0]) || '\\' === $value[0]: + return self::T_IDENTIFIER; + + case ',' === $value: + return self::T_COMMA; + + case '>' === $value: + return self::T_TYPE_END; + + case '<' === $value: + return self::T_TYPE_START; + + case ']' === $value: + return self::T_ARRAY_END; + + case '[' === $value: + return self::T_ARRAY_START; + + // Default + default: + // Do nothing + } + + return $type; + } +} diff --git a/src/Component/Serializer/Parser/JMSParser.php b/src/Component/Serializer/Parser/JMSParser.php new file mode 100644 index 0000000..5756f39 --- /dev/null +++ b/src/Component/Serializer/Parser/JMSParser.php @@ -0,0 +1,564 @@ + + * @author Pavel Kovalenko + * @see https://github.com/liip/metadata-parser + * @internal + * + * @SuppressWarnings(PHPMD) + */ +class JMSParser implements ModelParserInterface +{ + private const ACCESS_ORDER_CUSTOM = 'custom'; + + /** + * @var Reader + */ + private $annotationsReader; + + /** + * @var PhpTypeParser + */ + private $phpTypeParser; + + /** + * @var JMSTypeParser + */ + private $jmsTypeParser; + + /** + * JMSParser constructor. + * + * @param \Doctrine\Common\Annotations\Reader $annotationsReader + */ + public function __construct(Reader $annotationsReader) + { + $this->annotationsReader = $annotationsReader; + $this->phpTypeParser = new PhpTypeParser(); + $this->jmsTypeParser = new JMSTypeParser(); + } + + /** + * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata + */ + public function parse(RawClassMetadata $classMetadata): void + { + try { + $refClass = new ReflectionClass($classMetadata->getClassName()); // @phpstan-ignore-line + } catch (ReflectionException $exception) { + throw ParseException::classNotFound($classMetadata->getClassName(), $exception); + } + + $this->parseProperties($refClass, $classMetadata); + $this->parseMethods($refClass, $classMetadata); + $this->parseClass($refClass, $classMetadata); + } + + /** + * @param \ReflectionClass $refClass + * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata + * + * @phpstan-ignore-next-line + */ + private function parseProperties(ReflectionClass $refClass, RawClassMetadata $classMetadata): void + { + if ($refParentClass = $refClass->getParentClass()) { + $this->parseProperties($refParentClass, $classMetadata); + } + + foreach ($refClass->getProperties() as $refProperty) { + try { + $annotations = $this->annotationsReader->getPropertyAnnotations($refProperty); + } catch (AnnotationException $exception) { + throw ParseException::propertyError((string) $classMetadata, $refProperty->getName(), $exception); + } + + $property = $this->getProperty($classMetadata, $refProperty, $annotations); + $this->parsePropertyAnnotations($classMetadata, $property, $annotations); + } + } + + /** + * @param \ReflectionClass $refClass + * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata + * + * @phpstan-ignore-next-line + */ + private function parseMethods(ReflectionClass $refClass, RawClassMetadata $classMetadata): void + { + if ($refParentClass = $refClass->getParentClass()) { + $this->parseMethods($refParentClass, $classMetadata); + } + + foreach ($refClass->getMethods() as $refMethod) { + if (false === $refMethod->getDocComment()) { + continue; + } + + try { + $annotations = $this->annotationsReader->getMethodAnnotations($refMethod); + } catch (AnnotationException $exception) { + throw ParseException::propertyError((string) $classMetadata, $refMethod->getName(), $exception); + } + + if ($this->isVirtualProperty($annotations)) { + if (!$refMethod->isPublic()) { + throw ParseException::nonPublicMethod((string) $classMetadata, $refMethod->getName()); + } + + $methodName = $this->getMethodName($annotations, $refMethod); + $name = $this->getSerializedName($annotations) ?: $methodName; + + $property = new PropertyVariationMetadata($methodName, true, true); + $classMetadata->addPropertyVariation($name, $property); + + $property->setType($this->getReturnType($property, $refMethod, $refClass)); + $property->setAccessor(new PropertyAccessor($refMethod->getName(), null)); + + $this->parsePropertyAnnotations($classMetadata, $property, $annotations); + } + + if ($this->isPostDeserializeMethod($annotations)) { + if (!$refMethod->isPublic()) { + throw ParseException::nonPublicMethod((string) $classMetadata, $refMethod->getName()); + } + + $classMetadata->addPostDeserializeMethod($refMethod->getName()); + } + } + } + + /** + * @param \ReflectionClass $refClass + * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata + * + * @phpstan-ignore-next-line + */ + private function parseClass(ReflectionClass $refClass, RawClassMetadata $classMetadata): void + { + try { + $annotations = $this->gatherClassAnnotations($refClass); + } catch (AnnotationException $e) { + throw ParseException::classError($refClass->getName(), $e); + } + + foreach ($annotations as $annotation) { + switch (true) { + case $annotation instanceof AccessorOrder: + if (self::ACCESS_ORDER_CUSTOM !== $annotation->order) { + throw ParseException::unsupportedClassAnnotation( + (string) $classMetadata, + 'AccessorOrder::' . $annotation->order + ); + } + + // usort is not stable for the same result. we want to preserve order of + // the fields that are not explicitly mentioned + $order = []; + $init = count($annotation->custom); + foreach ($classMetadata->getPropertyCollections() as $property) { + $position = $property->getPosition($annotation->custom); + if (null === $position) { + $position = $init++; + } + $order[$property->getSerializedName()] = $position; + } + + $classMetadata->sortProperties(static function ( + PropertyCollection $propA, + PropertyCollection $propB + ) use ($order): int { + return $order[$propA->getSerializedName()] <=> $order[$propB->getSerializedName()]; + }); + break; + case $annotation instanceof ExclusionPolicy: + if (ExclusionPolicy::NONE !== $annotation->policy) { + throw ParseException::unsupportedClassAnnotation( + (string) $classMetadata, + 'ExclusionPolicy::' . $annotation->policy + ); + } + break; + default: + if ( + 0 === strncmp( + 'RetailCrm\Api\Component\Serializer\Annotation\\', + get_class($annotation), + mb_strlen('RetailCrm\Api\Component\Serializer\Annotation\\') + ) + ) { + // if there are annotations we can safely ignore, we need to explicitly ignore them + throw ParseException::unsupportedClassAnnotation( + (string) $classMetadata, + get_class($annotation) + ); + } + } + } + } + + /** + * Find the annotations we care about by looking through all ancestors of $reflectionClass. + * + * @param \ReflectionClass $reflectionClass + * + * @return object[] Hashmap of annotation class => annotation object + * @throws \Doctrine\Common\Annotations\AnnotationException + * + * @phpstan-ignore-next-line + */ + private function gatherClassAnnotations(ReflectionClass $reflectionClass): array + { + $map = []; + + if ($parent = $reflectionClass->getParentClass()) { + $map = $this->gatherClassAnnotations($parent); + } + + $annotations = $this->annotationsReader->getClassAnnotations($reflectionClass); + + foreach ($annotations as $annotation) { + $map[get_class($annotation)] = $annotation; + } + + return $map; + } + + /** + * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata + * @param \Liip\MetadataParser\ModelParser\RawMetadata\PropertyVariationMetadata $property + * @param array $annotations + */ + private function parsePropertyAnnotations( + RawClassMetadata $classMetadata, + PropertyVariationMetadata $property, + array $annotations + ): void { + foreach ($annotations as $annotation) { + switch (true) { + case $annotation instanceof Type: + try { + $type = $this->jmsTypeParser->parse($annotation->name); + } catch (InvalidTypeException $exception) { + throw ParseException::propertyTypeError( + (string) $classMetadata, + (string) $property, + $exception + ); + } + + if ($property->getType() instanceof PropertyTypeUnknown) { + $property->setType($type); + } else { + try { + $property->setType($property->getType()->merge($type)); + } catch (UnexpectedValueException $exception) { + throw ParseException::propertyTypeConflict( + (string) $classMetadata, + (string) $property, + (string) $property->getType(), + (string) $type, + $exception + ); + } + } + break; + case $annotation instanceof Exclude: + if (null !== $annotation->if) { + throw ParseException::unsupportedPropertyAnnotation( + (string) $classMetadata, + (string) $property, + 'Exclude::if' + ); + } + $classMetadata->removePropertyVariation((string) $property); + break; + case $annotation instanceof Groups: + $property->setGroups($annotation->groups); + break; + case $annotation instanceof Accessor: + $property->setAccessor(new PropertyAccessor($annotation->getter, $annotation->setter)); + break; + case $annotation instanceof Since: + $property->setVersionRange($property->getVersionRange()->withSince($annotation->version)); + break; + case $annotation instanceof Until: + $property->setVersionRange($property->getVersionRange()->withUntil($annotation->version)); + break; + case $annotation instanceof ReadOnly: + $property->setReadOnly(true); + break; + case $annotation instanceof SerializedName: + // we handle this separately + case $annotation instanceof VirtualProperty: + // we handle this separately + break; + default: + if (0 === strncmp('JMS\Serializer\\', get_class($annotation), mb_strlen('JMS\Serializer\\'))) { + // if there are annotations we can safely ignore, we need to explicitly ignore them + throw ParseException::unsupportedPropertyAnnotation( + (string) $classMetadata, + (string) $property, + get_class($annotation) + ); + } + break; + } + } + } + + /** + * Returns the property metadata for the specified property. + * + * If the property already exists on the class metadata this is returned. + * If the property has a serialized name that overrides the name of an existing property, + * it will be renamed and merged. + * + * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata + * @param \ReflectionProperty $refProperty + * @param array $annotations + * + * @return \Liip\MetadataParser\ModelParser\RawMetadata\PropertyVariationMetadata + * @throws \ReflectionException + */ + private function getProperty( + RawClassMetadata $classMetadata, + ReflectionProperty $refProperty, + array $annotations + ): PropertyVariationMetadata { + $defaultName = PropertyCollection::serializedName($refProperty->getName()); + $name = $this->getSerializedName($annotations) ?: $defaultName; + + if ($classMetadata->hasPropertyVariation($refProperty->getName())) { + $property = $classMetadata->getPropertyVariation($refProperty->getName()); + + if ($defaultName !== $name && $classMetadata->hasPropertyCollection($defaultName)) { + $classMetadata->removePropertyVariation($defaultName); + $this->addPropertyVariation($defaultName, $name, $property, $classMetadata); + } + } else { + $property = PropertyVariationMetadata::fromReflection($refProperty); + $this->addPropertyVariation($defaultName, $name, $property, $classMetadata); + } + + return $property; + } + + /** + * This workaround helps to avoid unnecessary camelCase to snake_case conversion while + * using default property metadata classes. This allows us to produce code we expect + * without rewriting the whole metadata parsing library. + * + * @param string $defaultName + * @param string $name + * @param \Liip\MetadataParser\ModelParser\RawMetadata\PropertyVariationMetadata $property + * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata + * + * @throws \ReflectionException + */ + private function addPropertyVariation( + string $defaultName, + string $name, + PropertyVariationMetadata $property, + RawClassMetadata $classMetadata + ): void { + if ($classMetadata->hasPropertyCollection($defaultName)) { + $prop = $classMetadata->getPropertyCollection($defaultName); + } else { + $prop = new PropertyCollection($name); + $classMetadata->addPropertyCollection($prop); + } + + $propName = new ReflectionProperty(get_class($prop), 'serializedName'); + $propName->setAccessible(true); + $propName->setValue($prop, $name); + + $prop->addVariation($property); + } + + /** + * @param \Liip\MetadataParser\ModelParser\RawMetadata\PropertyVariationMetadata $property + * @param \ReflectionMethod $refMethod + * @param \ReflectionClass $refClass + * + * @return \Liip\MetadataParser\Metadata\PropertyType + * + * @phpstan-ignore-next-line + */ + private function getReturnType( + PropertyVariationMetadata $property, + ReflectionMethod $refMethod, + ReflectionClass $refClass + ): PropertyType { + $type = new PropertyTypeUnknown(true); + $refType = $refMethod->getReturnType(); + + if (null !== $refType) { + $type = $this->phpTypeParser->parseReflectionType($refType); + } + + try { + $docBlockType = $this->getReturnTypeOfMethod($refMethod, $refClass); + } catch (InvalidTypeException $exception) { + throw ParseException::propertyTypeError($refClass->getName(), (string) $property, $exception); + } + + if (null === $docBlockType) { + return $type; + } + + try { + return $type->merge($docBlockType); + } catch (UnexpectedValueException $exception) { + throw ParseException::propertyTypeConflict( + $refClass->getName(), + (string) $property, + (string) $type, + (string) $docBlockType, + $exception + ); + } + } + + /** + * @param \ReflectionMethod $refMethod + * @param \ReflectionClass $refClass + * + * @return \Liip\MetadataParser\Metadata\PropertyType|null + * + * @phpstan-ignore-next-line + */ + private function getReturnTypeOfMethod(ReflectionMethod $refMethod, ReflectionClass $refClass): ?PropertyType + { + $docComment = $refMethod->getDocComment(); + + if (false === $docComment) { + return null; + } + + foreach (explode("\n", $docComment) as $line) { + if (1 === preg_match('/@return ([^ ]+)/', $line, $matches)) { + return $this->phpTypeParser->parseAnnotationType($matches[1], $refClass); + } + } + + return null; + } + + /** + * @param array $annotations + * + * @return string|null + */ + private function getSerializedName(array $annotations): ?string + { + foreach ($annotations as $annotation) { + if ($annotation instanceof SerializedName) { + return $annotation->name; + } + } + + return null; + } + + /** + * @param array $annotations + * + * @return bool + */ + private function isVirtualProperty(array $annotations): bool + { + foreach ($annotations as $annotation) { + if ($annotation instanceof VirtualProperty) { + return true; + } + } + + return false; + } + + /** + * @param array $annotations + * + * @return bool + */ + private function isPostDeserializeMethod(array $annotations): bool + { + foreach ($annotations as $annotation) { + if ($annotation instanceof PostDeserialize) { + return true; + } + } + + return false; + } + + /** + * @param array $annotations + * @param \ReflectionMethod $refMethod + * + * @return string + */ + private function getMethodName(array $annotations, ReflectionMethod $refMethod): string + { + $name = $refMethod->getName(); + + foreach ($annotations as $annotation) { + if ($annotation instanceof VirtualProperty && null !== $annotation->name) { + $name = $annotation->name; + break; + } + } + + if (0 === strpos($name, 'get')) { + $name = lcfirst(substr($name, 3)); + } + + return $name; + } +} diff --git a/src/Component/Serializer/Parser/JMSTypeParser.php b/src/Component/Serializer/Parser/JMSTypeParser.php new file mode 100644 index 0000000..ecb86f9 --- /dev/null +++ b/src/Component/Serializer/Parser/JMSTypeParser.php @@ -0,0 +1,142 @@ + + * @author Pavel Kovalenko + * @see https://github.com/liip/metadata-parser + * @internal + * + * @SuppressWarnings(PHPMD) + */ +class JMSTypeParser +{ + private const TYPE_ARRAY = 'array'; + + /** + * @var \RetailCrm\Api\Component\Serializer\Parser\BaseJMSParser + */ + private $jmsTypeParser; + + /** + * JMSTypeParser constructor. + */ + public function __construct() + { + $this->jmsTypeParser = new BaseJMSParser(); + } + + /** + * @param string $rawType + * + * @return \Liip\MetadataParser\Metadata\PropertyType + */ + public function parse(string $rawType): PropertyType + { + if ('' === $rawType) { + return new PropertyTypeUnknown(true); + } + + return $this->parseType($this->jmsTypeParser->parse($rawType)); + } + + /** + * @param array $typeInfo + * @param bool $isSubType + * + * @return \Liip\MetadataParser\Metadata\PropertyType + */ + private function parseType(array $typeInfo, bool $isSubType = false): PropertyType + { + $typeInfo = array_merge( + [ + 'name' => null, + 'params' => [], + ], + $typeInfo + ); + + // JMS types are nullable except if it's a sub type (part of array) + $nullable = !$isSubType; + + if (0 === \count($typeInfo['params'])) { + if (self::TYPE_ARRAY === $typeInfo['name']) { + return new PropertyTypeArray(new PropertyTypeUnknown(false), false, $nullable); + } + + if (PropertyTypePrimitive::isTypePrimitive($typeInfo['name'])) { + return new PropertyTypePrimitive($typeInfo['name'], $nullable); + } + + if (PropertyTypeDateTime::isTypeDateTime($typeInfo['name'])) { + return PropertyTypeDateTime::fromDateTimeClass($typeInfo['name'], $nullable); + } + + if (PropertyTypeMixed::TYPE_NAME === $typeInfo['name']) { + return new PropertyTypeMixed(); + } + + return new PropertyTypeClass($typeInfo['name'], $nullable); + } + + if (self::TYPE_ARRAY === $typeInfo['name']) { + if (1 === \count($typeInfo['params'])) { + return new PropertyTypeArray( + $this->parseType($typeInfo['params'][0], true), + false, + $nullable + ); + } + if (2 === \count($typeInfo['params'])) { + return new PropertyTypeArray( + $this->parseType($typeInfo['params'][1], true), + true, + $nullable + ); + } + + throw new InvalidTypeException(sprintf( + 'JMS property type array can\'t have more than 2 parameters (%s)', + var_export($typeInfo, true) + )); + } + + if (PropertyTypeDateTime::isTypeDateTime($typeInfo['name'])) { + // the case of datetime without params is already handled above, we know we have params + return PropertyTypeDateTime::fromDateTimeClass( + $typeInfo['name'], + $nullable, + new DateTimeOptions( + $typeInfo['params'][0] ?: null, + ($typeInfo['params'][1] ?? null) ?: null, + ($typeInfo['params'][2] ?? null) ?: null + ) + ); + } + + throw new InvalidTypeException(sprintf('Unknown JMS property found (%s)', var_export($typeInfo, true))); + } +} diff --git a/src/Component/Serializer/Template/AbstractTemplate.php b/src/Component/Serializer/Template/AbstractTemplate.php new file mode 100644 index 0000000..b141358 --- /dev/null +++ b/src/Component/Serializer/Template/AbstractTemplate.php @@ -0,0 +1,51 @@ +twig = new Environment(new ArrayLoader(), ['autoescape' => false]); + } + + /** + * @param string $template + * @param array $parameters + * + * @return string + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\SyntaxError + */ + protected function render(string $template, array $parameters): string + { + $tmpl = $this->twig->createTemplate($template); + + return $tmpl->render($parameters); + } +} diff --git a/src/Component/Serializer/Template/CustomDeserialization.php b/src/Component/Serializer/Template/CustomDeserialization.php new file mode 100644 index 0000000..e5afb6e --- /dev/null +++ b/src/Component/Serializer/Template/CustomDeserialization.php @@ -0,0 +1,67 @@ + $arguments + * @param string $customerCode + * @param string $corporateCode + * @param string $initArgumentsCode + * + * @return string + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\SyntaxError + */ + public function renderCustomerInterface( + string $arrayPath, + string $modelPath, + string $className, + array $arguments, + string $customerCode, + string $corporateCode, + string $initArgumentsCode = '' + ): string { + return $this->render(self::TMPL_CUSTOMER_INTERFACE, [ + 'arrayPath' => $arrayPath, + 'modelPath' => $modelPath, + 'className' => $className, + 'arguments' => $arguments, + 'customerCode' => $customerCode, + 'corporateCode' => $corporateCode, + 'initArgumentsCode' => $initArgumentsCode, + ]); + } +} diff --git a/src/Component/Serializer/Template/CustomSerialization.php b/src/Component/Serializer/Template/CustomSerialization.php new file mode 100644 index 0000000..3527c1e --- /dev/null +++ b/src/Component/Serializer/Template/CustomSerialization.php @@ -0,0 +1,61 @@ +render(self::TMPL_CUSTOMER_INTERFACE, [ + 'jsonPath' => $jsonPath, + 'modelPath' => $modelPath, + 'customerCode' => $customerCode, + 'corporateCode' => $corporateCode, + ]); + } +} diff --git a/src/Component/Serializer/Type/PropertyTypeMixed.php b/src/Component/Serializer/Type/PropertyTypeMixed.php new file mode 100644 index 0000000..5b2b0d1 --- /dev/null +++ b/src/Component/Serializer/Type/PropertyTypeMixed.php @@ -0,0 +1,57 @@ +format(static::FORMAT_DATE_TIME); + } + + /** + * Converts provided DateTimeInterface instance to date format used in the system. + * + * @param \DateTimeInterface $dateTime + * + * @return string + */ + public static function formatDate(DateTimeInterface $dateTime): string + { + return $dateTime->format(static::FORMAT_DATE); + } + + /** + * Converts provided DateTimeInterface instance to time format used in the system. + * + * @param \DateTimeInterface $dateTime + * + * @return string + */ + public static function formatTime(DateTimeInterface $dateTime): string + { + return $dateTime->format(static::FORMAT_TIME); + } + + /** + * @param string $item + * @param string $format + * + * @return \DateTime + */ + private static function createFromFormat(string $item, string $format): DateTime + { + $result = DateTime::createFromFormat($format, $item); + + if (!($result instanceof DateTime)) { + throw new InvalidArgumentException(sprintf( + 'Invalid date format: expect "%s" got "%s".', + $format, + $item + )); + } + + return $result; + } +} diff --git a/src/Component/Transformer/RequestTransformer.php b/src/Component/Transformer/RequestTransformer.php new file mode 100644 index 0000000..83f093b --- /dev/null +++ b/src/Component/Transformer/RequestTransformer.php @@ -0,0 +1,76 @@ +handler = $handler; + } + + /** + * Transforms provided request data into PSR-7 request model. + * + * You can alter the results by providing your chain of handlers. + * + * @param string $method + * @param string $uri + * @param \RetailCrm\Api\Interfaces\RequestInterface|null $request + * + * @return \Psr\Http\Message\RequestInterface + * @throws \RetailCrm\Api\Exception\Client\HandlerException + */ + public function createPsrRequest( + string $method, + string $uri, + ?RequestInterface $request = null + ): PsrRequestInterface { + $requestData = new RequestData($method, $uri, $request); + $this->handler->handle($requestData); + + if (null === $requestData->request) { + throw new HandlerException('Handlers should instantiate request in the ResponseData.'); + } + + return $requestData->request; + } + + /** + * @return \RetailCrm\Api\Interfaces\HandlerInterface|null + */ + public function getHandler(): ?HandlerInterface + { + return $this->handler; + } +} diff --git a/src/Component/Transformer/ResponseTransformer.php b/src/Component/Transformer/ResponseTransformer.php new file mode 100644 index 0000000..cd43cf6 --- /dev/null +++ b/src/Component/Transformer/ResponseTransformer.php @@ -0,0 +1,75 @@ +handler = $handler; + } + + /** + * Transforms PSR-7 response into response model. + * + * You can alter the results by providing your chain of handlers. + * + * @param string $baseUrl + * @param \Psr\Http\Message\RequestInterface $request + * @param \Psr\Http\Message\ResponseInterface $response + * @param string $type + * + * @return RetailCrmResponse + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + */ + public function createResponse( + string $baseUrl, + RequestInterface $request, + ResponseInterface $response, + string $type + ): RetailCrmResponse { + $responseData = new ResponseData($baseUrl, $request, $response, $type); + $this->handler->handle($responseData); + + return $responseData->responseModel; + } + + /** + * @inheritDoc + */ + public function getHandler(): ?HandlerInterface + { + return $this->handler; + } +} diff --git a/src/Component/Utils.php b/src/Component/Utils.php new file mode 100644 index 0000000..9275375 --- /dev/null +++ b/src/Component/Utils.php @@ -0,0 +1,79 @@ +isSeekable() ? $stream->__toString() : $stream->getContents(); + } + + /** + * Returns target directory for the model cache. + * + * @return string + */ + public static function getModelsCacheDirectory(): string + { + $parentDir = implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..']); + + return implode(DIRECTORY_SEPARATOR, [realpath($parentDir), 'models']); + } + + /** + * Returns models directory. + * + * @return string + */ + public static function getModelsDirectory(): string + { + return (string) realpath(implode(DIRECTORY_SEPARATOR, [dirname(__DIR__), '..', 'src', 'Model'])); + } +} diff --git a/src/Enum/ByIdentifier.php b/src/Enum/ByIdentifier.php new file mode 100644 index 0000000..131a093 --- /dev/null +++ b/src/Enum/ByIdentifier.php @@ -0,0 +1,22 @@ +requestScheme = (string) parse_url($baseUrl, PHP_URL_SCHEME); + $this->crmDomain = (string) parse_url($baseUrl, PHP_URL_HOST); + $this->request = $request; + $this->response = $response; + } + + /** + * Returns a request for which an event was thrown. + * + * @return \Psr\Http\Message\RequestInterface + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Returns a response for which an event was thrown. + * + * @return \Psr\Http\Message\ResponseInterface|null + */ + public function getResponse(): ?ResponseInterface + { + return $this->response; + } + + /** + * Returns RetailCRM domain for request. + * + * @return string + */ + public function getApiDomain(): string + { + return $this->crmDomain; + } + + /** + * Returns RetailCRM URL for request in the event without trailing slash. + * + * @return string + */ + public function getApiUrl(): string + { + return sprintf('%s://%s', $this->requestScheme, $this->crmDomain); + } + + /** + * Returns API key for request in the event. + * + * @return string + */ + public function getApiKey(): string + { + if (empty($this->apiKey)) { + $uri = $this->request->getUri(); + + if ('' !== $this->request->getHeaderLine('X-Api-Key')) { + $this->apiKey = $this->request->getHeaderLine('X-Api-Key'); + } elseif ('' !== $uri->getQuery()) { + $params = []; + parse_str($uri->getQuery(), $params); + + if (array_key_exists('apiKey', $params)) { + $this->apiKey = $params['apiKey']; + } + } + } + + return $this->apiKey; + } +} diff --git a/src/Event/FailureRequestEvent.php b/src/Event/FailureRequestEvent.php new file mode 100644 index 0000000..b439fb3 --- /dev/null +++ b/src/Event/FailureRequestEvent.php @@ -0,0 +1,76 @@ +exception = $exception; + } + + /** + * Suppresses exception for the client caller. + * + * @return FailureRequestEvent + */ + public function suppressThrow(): FailureRequestEvent + { + $this->suppressThrow = true; + return $this; + } + + /** + * Returns true if exception should not be thrown. + * + * @return bool + */ + public function shouldSuppressThrow(): bool + { + return $this->suppressThrow; + } + + /** + * Returns an exception instance which will be thrown after event propagation (if not suppressed). + * + * @return \RetailCrm\Api\Exception\ApiException|\RetailCrm\Api\Exception\ClientException + */ + public function getException() + { + return $this->exception; + } +} diff --git a/src/Event/SuccessRequestEvent.php b/src/Event/SuccessRequestEvent.php new file mode 100644 index 0000000..7ddd0eb --- /dev/null +++ b/src/Event/SuccessRequestEvent.php @@ -0,0 +1,53 @@ +responseModel = $responseModel; + } + + /** + * @return \RetailCrm\Api\Interfaces\ResponseInterface + */ + public function getResponseModel(): ResponseInterface + { + return $this->responseModel; + } +} diff --git a/src/Exception/Api/AccessDeniedException.php b/src/Exception/Api/AccessDeniedException.php new file mode 100644 index 0000000..ab589c4 --- /dev/null +++ b/src/Exception/Api/AccessDeniedException.php @@ -0,0 +1,22 @@ +response = $errorResponse instanceof ErrorResponse ? $errorResponse : new ErrorResponse(); + + parent::__construct(static::getErrorMessage($this->response), $statusCode, $previous); + } + + /** + * Returns response status code + * + * @return int + */ + public function getStatusCode(): int + { + return $this->code; + } + + /** + * Returns ErrorResponse. + * + * @return \RetailCrm\Api\Model\Response\ErrorResponse + */ + public function getErrorResponse(): ErrorResponse + { + return $this->response; + } + + /** + * @return string + */ + public function __toString(): string + { + $base = sprintf( + "API Exception with message '%s' in %s:%d\nResponse status code: %d\n", + $this->getMessage(), + $this->getFile(), + $this->getLine(), + $this->getStatusCode() + ); + $errorsList = $this->getErrorResponse()->errors; + + if (null !== $errorsList && count($errorsList) > 0) { + $errors = []; + + foreach ($this->getErrorResponse()->errors as $key => $error) { + $errors[] = sprintf(' - %s: %s', $key, $error); + } + + $base .= sprintf("Errors:\n%s\n", implode(PHP_EOL, $errors)); + } + + return trim($base . sprintf("Stacktrace: %s", $this->getTraceAsString())); + } + + /** + * Returns the error message. + * + * @param \RetailCrm\Api\Model\Response\ErrorResponse $errorResponse + * + * @return string + */ + private static function getErrorMessage(ErrorResponse $errorResponse): string + { + if (!empty($errorResponse->errorMsg)) { + return $errorResponse->errorMsg; + } + + if (!empty($errorResponse->errors)) { + return (string) reset($errorResponse->errors); + } + + return 'RetailCRM API Error'; + } +} diff --git a/src/Exception/Client/BuilderException.php b/src/Exception/Client/BuilderException.php new file mode 100644 index 0000000..284e740 --- /dev/null +++ b/src/Exception/Client/BuilderException.php @@ -0,0 +1,50 @@ +invalidArguments = $invalidArguments; + } + + /** + * Returns invalid arguments list. + * + * @return string[] + */ + public function getInvalidArgument(): array + { + return $this->invalidArguments; + } +} diff --git a/src/Exception/Client/HandlerException.php b/src/Exception/Client/HandlerException.php new file mode 100644 index 0000000..31c9ea1 --- /dev/null +++ b/src/Exception/Client/HandlerException.php @@ -0,0 +1,22 @@ +getMessage(), $this->getCode(), $this->getTraceAsString()); + } +} diff --git a/src/Factory/ApiExceptionFactory.php b/src/Factory/ApiExceptionFactory.php new file mode 100644 index 0000000..4c9240b --- /dev/null +++ b/src/Factory/ApiExceptionFactory.php @@ -0,0 +1,85 @@ + MissingCredentialsException::class, + 'Wrong "apiKey" value.' => InvalidCredentialsException::class, + 'Access denied.' => AccessDeniedException::class, + 'Account does not exist.' => AccountDoesNotExistException::class, + 'Errors in the entity format' => ValidationException::class, + 'Validation error' => ValidationException::class, + ]; + + /** + * Create exception from API response. + * + * @param \RetailCrm\Api\Interfaces\ResponseInterface $errorResponse + * @param int $statusCode + * @param \Throwable|null $previous + * + * @return \RetailCrm\Api\Exception\ApiException + */ + public function createException( + ResponseInterface $errorResponse, + int $statusCode = 0, + Throwable $previous = null + ): ApiException { + $response = $errorResponse instanceof ErrorResponse ? $errorResponse : new ErrorResponse(); + $errorFqn = self::getErrorClassByMessage($response->errorMsg ?? ''); + + if (class_exists($errorFqn) && is_subclass_of($errorFqn, ApiException::class)) { + return new $errorFqn($response, $statusCode, $previous); + } + + return new ApiErrorException($response, $statusCode, $previous); + } + + /** + * Returns error class by error message. + * + * @param string $message + * + * @return string + */ + private static function getErrorClassByMessage(string $message): string + { + if (array_key_exists($message, self::$errorTypesMapping)) { + return self::$errorTypesMapping[$message]; + } + + if (preg_match('/^Parameter \'[\w\]\[\_\-]+\' is missing$/', $message)) { + return MissingParameterException::class; + } + + return ApiErrorException::class; + } +} diff --git a/src/Factory/ClientFactory.php b/src/Factory/ClientFactory.php new file mode 100644 index 0000000..fbacdd8 --- /dev/null +++ b/src/Factory/ClientFactory.php @@ -0,0 +1,335 @@ +cacheDir = $cacheDir; + return $this; + } + + /** + * Sets cache implementation which will be used to store metadata cache. + * + * @param \Psr\Cache\CacheItemPoolInterface $cache + * + * @return ClientFactory + */ + public function setCache(CacheItemPoolInterface $cache): ClientFactory + { + $this->cache = $cache; + return $this; + } + + /** + * Sets debug logger + * + * @param \Psr\Log\LoggerInterface $debugLogger + * + * @return ClientFactory + */ + public function setDebugLogger(LoggerInterface $debugLogger): ClientFactory + { + $this->debugLogger = $debugLogger; + return $this; + } + + /** + * Set your PSR-18 HTTP client. + * + * Service discovery will be used if no client has been provided. + * + * @param \Psr\Http\Client\ClientInterface $httpClient + * + * @return ClientFactory + */ + public function setHttpClient(ClientInterface $httpClient): ClientFactory + { + $this->httpClient = $httpClient; + return $this; + } + + /** + * Sets PSR-17 compatible stream factory. You can skip this step if you want to use service discovery. + * + * @param \Psr\Http\Message\StreamFactoryInterface|null $streamFactory + * + * @return ClientFactory + */ + public function setStreamFactory(?StreamFactoryInterface $streamFactory): ClientFactory + { + $this->streamFactory = $streamFactory; + return $this; + } + + /** + * Sets PSR-17 compatible request factory. You can skip this step if you want to use service discovery. + * + * @param \Psr\Http\Message\RequestFactoryInterface|null $requestFactory + * + * @return ClientFactory + */ + public function setRequestFactory(?RequestFactoryInterface $requestFactory): ClientFactory + { + $this->requestFactory = $requestFactory; + return $this; + } + + /** + * Sets PSR-17 compatible URI factory. You can skip this step if you want to use service discovery. + * + * @param \Psr\Http\Message\UriFactoryInterface|null $uriFactory + * + * @return ClientFactory + */ + public function setUriFactory(?UriFactoryInterface $uriFactory): ClientFactory + { + $this->uriFactory = $uriFactory; + return $this; + } + + /** + * Appends an additional request handler into the request processing chain. + * + * @param \RetailCrm\Api\Interfaces\HandlerInterface $handler + * + * @return ClientFactory + */ + public function appendRequestHandler(HandlerInterface $handler): ClientFactory + { + $this->requestHandlers[] = $handler; + return $this; + } + + /** + * Appends an additional handler into the response processing chain. + * + * @param \RetailCrm\Api\Interfaces\HandlerInterface $handler + * + * @return ClientFactory + */ + public function appendResponseHandler(HandlerInterface $handler): ClientFactory + { + $this->responseHandlers[] = $handler; + return $this; + } + + /** + * Appends additional request handlers into the request processing chain. + * + * @param \RetailCrm\Api\Interfaces\HandlerInterface[] $handlers + * + * @return ClientFactory + */ + public function appendRequestHandlers(array $handlers): ClientFactory + { + foreach ($handlers as $handler) { + $this->appendRequestHandler($handler); + } + + return $this; + } + + /** + * Appends additional response handlers into the response processing chain. + * + * @param \RetailCrm\Api\Interfaces\HandlerInterface[] $handlers + * + * @return ClientFactory + */ + public function appendResponseHandlers(array $handlers): ClientFactory + { + foreach ($handlers as $handler) { + $this->appendResponseHandler($handler); + } + + return $this; + } + + /** + * Instantiates a new instance of Client. + * + * @param string $apiUrl + * @param string $apiKey + * + * @return \RetailCrm\Api\Client + * @throws \RetailCrm\Api\Exception\Client\BuilderException + */ + public function createClient(string $apiUrl, string $apiKey): Client + { + if (null === $this->formEncoder) { + $this->formEncoder = $this->buildFormEncoder(); + } + + if (null === $this->responseTransformer) { + $this->responseTransformer = $this->buildResponseTransformer(); + } + + return (new ClientBuilder()) + ->setApiUrl($apiUrl) + ->setAuthenticatorHandler(new HeaderAuthenticatorHandler($apiKey)) + ->setFormEncoder($this->formEncoder) + ->setResponseTransformer($this->responseTransformer) + ->setDebugLogger($this->debugLogger) + ->setHttpClient($this->httpClient) + ->setStreamFactory($this->streamFactory) + ->setRequestFactory($this->requestFactory) + ->setUriFactory($this->uriFactory) + ->appendRequestHandlers($this->requestHandlers) + ->appendResponseHandlers($this->responseHandlers) + ->build(); + } + + /** + * Builds FormEncoder instance. + * + * @throws \RetailCrm\Api\Exception\Client\BuilderException + */ + private function buildFormEncoder(): FormEncoderInterface + { + $builder = new FormEncoderBuilder(); + + if (!empty($this->cacheDir)) { + $builder->setCacheDir($this->cacheDir); + } + + if (null !== $this->cache) { + $builder->setCache($this->cache); + } + + return $builder->build(); + } + + /** + * Builds ResponseTransformer instance. + * + * @return \RetailCrm\Api\Interfaces\ResponseTransformerInterface + * @throws \RetailCrm\Api\Exception\Client\BuilderException + */ + private function buildResponseTransformer(): ResponseTransformerInterface + { + if (null === $this->formEncoder) { + throw new BuilderException('FormEncoder must exist to create ResponseTransformer.'); + } + + if (null === $this->apiExceptionFactory) { + $this->apiExceptionFactory = new ApiExceptionFactory(); + } + + return new ResponseTransformer(ResponsePipelineFactory::createDefaultPipeline( + $this->formEncoder->getSerializer(), + $this->apiExceptionFactory, + $this->eventDispatcher + )); + } +} diff --git a/src/Factory/RequestPipelineFactory.php b/src/Factory/RequestPipelineFactory.php new file mode 100644 index 0000000..790bbec --- /dev/null +++ b/src/Factory/RequestPipelineFactory.php @@ -0,0 +1,69 @@ +setNext(new RequestDataHandler($formEncoder, $streamFactory)); + + if (count($additionalHandlers) > 0) { + foreach ($additionalHandlers as $additionalHandler) { + if ($additionalHandler instanceof PsrFactoriesAwareInterface) { + $additionalHandler->setRequestFactory($requestFactory); + $additionalHandler->setStreamFactory($streamFactory); + $additionalHandler->setUriFactory($uriFactory); + } + + $nextHandler = $nextHandler->setNext($additionalHandler); + } + } + + return $handler; + } +} diff --git a/src/Factory/ResponsePipelineFactory.php b/src/Factory/ResponsePipelineFactory.php new file mode 100644 index 0000000..73c49e0 --- /dev/null +++ b/src/Factory/ResponsePipelineFactory.php @@ -0,0 +1,73 @@ +setNext(new ErrorResponseHandler($serializer, $exceptionFactory, $eventDispatcher)) + ->setNext(new FilesDownloadResponseHandler($serializer, $exceptionFactory, $eventDispatcher)) + ->setNext(new UnmarshalResponseHandler($serializer, $exceptionFactory, $eventDispatcher)); + + if (count($additionalHandlers) > 0) { + foreach ($additionalHandlers as $additionalHandler) { + if ($additionalHandler instanceof SerializerAwareInterface) { + $additionalHandler->setSerializer($serializer); + } + + if ($additionalHandler instanceof ApiExceptionFactoryAwareInterface) { + $additionalHandler->setApiExceptionFactory($exceptionFactory); + } + + if ($additionalHandler instanceof EventDispatcherAwareInterface) { + $additionalHandler->setEventDispatcher($eventDispatcher); + } + + $nextHandler = $nextHandler->setNext($additionalHandler); + } + } + + return $handler; + } +} diff --git a/src/Factory/SerializerFactory.php b/src/Factory/SerializerFactory.php new file mode 100644 index 0000000..e0fbb2e --- /dev/null +++ b/src/Factory/SerializerFactory.php @@ -0,0 +1,34 @@ +setApiUrl($apiUrl) + ->setAuthenticatorHandler(new HeaderAuthenticatorHandler($apiKey)) + ->setFormEncoder((new FormEncoderBuilder())->build()) + ->build(); + } + + /** + * Instantiates Client with provided URL, key and cache implementation. + * + * @param string $apiUrl + * @param string $apiKey + * @param \Psr\Cache\CacheItemPoolInterface $cache + * + * @return \RetailCrm\Api\Client + * @throws \RetailCrm\Api\Exception\Client\BuilderException + */ + public static function createWithCache(string $apiUrl, string $apiKey, CacheItemPoolInterface $cache): Client + { + return (new ClientBuilder()) + ->setApiUrl($apiUrl) + ->setAuthenticatorHandler(new HeaderAuthenticatorHandler($apiKey)) + ->setFormEncoder( + (new FormEncoderBuilder()) + ->setCache($cache) + ->build() + ) + ->build(); + } + + /** + * Instantiates Client with provided URL, key and cache directory. + * + * @param string $apiUrl + * @param string $apiKey + * @param string $cacheDir + * + * @return \RetailCrm\Api\Client + * @throws \RetailCrm\Api\Exception\Client\BuilderException + */ + public static function createWithCacheDir(string $apiUrl, string $apiKey, string $cacheDir): Client + { + return (new ClientBuilder()) + ->setApiUrl($apiUrl) + ->setAuthenticatorHandler(new HeaderAuthenticatorHandler($apiKey)) + ->setFormEncoder( + (new FormEncoderBuilder()) + ->setCacheDir($cacheDir) + ->build() + ) + ->build(); + } +} diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php new file mode 100644 index 0000000..92b426f --- /dev/null +++ b/src/Handler/AbstractHandler.php @@ -0,0 +1,81 @@ +next) { + return $this->next->handle($item); + } + + return null; + } + + /** + * @inheritDoc + */ + public function setNext(HandlerInterface $handler): HandlerInterface + { + $this->next = $handler; + return $handler; + } + + /** + * @inheritDoc + */ + public function getNext(): ?HandlerInterface + { + return $this->next; + } + + /** + * @inheritDoc + */ + public function append(HandlerInterface $handler): HandlerInterface + { + $this->getLastHandler()->setNext($handler); + + return $handler; + } + + /** + * @inheritDoc + */ + public function getLastHandler(): HandlerInterface + { + $lastHandler = $this; + + while (null !== $lastHandler->getNext()) { + $lastHandler = $lastHandler->getNext(); + } + + return $lastHandler; + } +} diff --git a/src/Handler/Request/CallbackRequestHandler.php b/src/Handler/Request/CallbackRequestHandler.php new file mode 100644 index 0000000..9b05de7 --- /dev/null +++ b/src/Handler/Request/CallbackRequestHandler.php @@ -0,0 +1,54 @@ +callback = $callback; + } + + /** + * @inheritDoc + */ + public function handle($item) + { + call_user_func( + $this->callback, + $item, + $this->requestFactory, + $this->streamFactory, + $this->uriFactory + ); + + return parent::handle($item); + } +} diff --git a/src/Handler/Request/GetParameterAuthenticatorHandler.php b/src/Handler/Request/GetParameterAuthenticatorHandler.php new file mode 100644 index 0000000..106b29b --- /dev/null +++ b/src/Handler/Request/GetParameterAuthenticatorHandler.php @@ -0,0 +1,58 @@ +apiKey = $apiKey; + } + + /** + * Adds "apiKey" GET parameter with provided API key + * + * @param mixed $item + * + * @return mixed|null + * @throws \RetailCrm\Api\Exception\Client\HandlerException|\RetailCrm\Api\Interfaces\ApiExceptionInterface + */ + public function handle($item) + { + if ($item instanceof RequestData && null !== $item->request) { + $params = []; + $uri = $item->request->getUri(); + parse_str($uri->getQuery(), $params); + + $params['apiKey'] = $this->apiKey; + + $item->request = $item->request->withUri($uri->withQuery(http_build_query($params)), true); + } + + return parent::handle($item); + } +} diff --git a/src/Handler/Request/HeaderAuthenticatorHandler.php b/src/Handler/Request/HeaderAuthenticatorHandler.php new file mode 100644 index 0000000..6cc4590 --- /dev/null +++ b/src/Handler/Request/HeaderAuthenticatorHandler.php @@ -0,0 +1,52 @@ +apiKey = $apiKey; + } + + /** + * Adds "x-api-key" header with provided API key + * + * @param mixed $item + * + * @return mixed|null + * @throws \RetailCrm\Api\Exception\Client\HandlerException|\RetailCrm\Api\Interfaces\ApiExceptionInterface + */ + public function handle($item) + { + if ($item instanceof RequestData && null !== $item->request) { + $item->request = $item->request->withAddedHeader('X-Api-Key', $this->apiKey); + } + + return parent::handle($item); + } +} diff --git a/src/Handler/Request/PsrRequestHandler.php b/src/Handler/Request/PsrRequestHandler.php new file mode 100644 index 0000000..4de676a --- /dev/null +++ b/src/Handler/Request/PsrRequestHandler.php @@ -0,0 +1,84 @@ +uriFactory = $uriFactory; + $this->requestFactory = $requestFactory; + } + + /** + * Creates base PSR-7 request model with URI and method. + * + * @param mixed $item + * + * @return mixed|null + * @throws \RetailCrm\Api\Exception\Client\HandlerException|\RetailCrm\Api\Interfaces\ApiExceptionInterface + */ + public function handle($item) + { + if ($item instanceof RequestData) { + $item->method = strtoupper($item->method); + + try { + $uri = $this->uriFactory->createUri($item->uri); + } catch (InvalidArgumentException $exception) { + throw new HandlerException("Cannot parse URI", 0, $exception); + } + + $request = $this->requestFactory + ->createRequest($item->method, $uri) + ->withHeader('User-Agent', 'RetailCRM PHP API Client / v6.x') + ->withHeader('Accept', 'application/json'); + + if (in_array($item->method, static::$methodsWithBody)) { + $request = $request->withHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + + $item->request = $request; + } + + return parent::handle($item); + } +} diff --git a/src/Handler/Request/RequestDataHandler.php b/src/Handler/Request/RequestDataHandler.php new file mode 100644 index 0000000..db22b9a --- /dev/null +++ b/src/Handler/Request/RequestDataHandler.php @@ -0,0 +1,86 @@ +formEncoder = $formEncoder; + $this->streamFactory = $streamFactory; + } + + /** + * Fills PSR-7 compatible request with request data. + * + * @param mixed $item + * + * @return mixed|null + * @throws \RetailCrm\Api\Exception\Client\HandlerException|\RetailCrm\Api\Interfaces\ApiExceptionInterface + * + * @SuppressWarnings(PHPMD.ElseExpression) + */ + public function handle($item) + { + if ($item instanceof RequestData && null !== $item->requestModel && null !== $item->request) { + try { + $formData = $this->formEncoder->encode($item->requestModel); + } catch (Throwable $throwable) { + throw new HandlerException( + sprintf('Cannot encode request: %s', $throwable->getMessage()), + $throwable->getCode(), + $throwable + ); + } + + if (in_array(strtoupper($item->request->getMethod()), [RequestMethod::GET, RequestMethod::DELETE], true)) { + $item->request = $item->request->withUri( + $item->request->getUri()->withQuery($formData) + ); + } else { + $item->request = $item->request->withBody( + $this->streamFactory->createStream($formData) + ); + } + } + + return parent::handle($item); + } +} diff --git a/src/Handler/Response/AbstractResponseHandler.php b/src/Handler/Response/AbstractResponseHandler.php new file mode 100644 index 0000000..a31da03 --- /dev/null +++ b/src/Handler/Response/AbstractResponseHandler.php @@ -0,0 +1,114 @@ +serializer = $serializer; + $this->apiExceptionFactory = $exceptionFactory; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * @inheritDoc + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + */ + final public function handle($item) + { + if ($item instanceof ResponseData) { + return $this->handleResponse($item); + } + + return parent::handle($item); + } + + /** + * Return to parent. + * + * @param mixed $item + * + * @return mixed|null + * @throws \RetailCrm\Api\Exception\Client\HandlerException|\RetailCrm\Api\Interfaces\ApiExceptionInterface + */ + protected function next($item) + { + return parent::handle($item); + } + + /** + * @param \Psr\Http\Message\ResponseInterface $response + * @param string $type + * + * @return RetailcrmResponse + * @throws \RetailCrm\Api\Exception\Client\HandlerException + */ + protected function unmarshalBody(ResponseInterface $response, string $type): RetailcrmResponse + { + try { + return $this->serializer->deserialize(Utils::getBodyContents($response->getBody()), $type, 'json'); + } catch (Throwable $throwable) { + throw new HandlerException('Cannot deserialize body: ' . $throwable->getMessage(), 0, $throwable); + } + } + + /** + * Handle response. + * + * @param \RetailCrm\Api\Model\ResponseData $responseData + * + * @return mixed + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + */ + abstract protected function handleResponse(ResponseData $responseData); +} diff --git a/src/Handler/Response/AccountNotFoundHandler.php b/src/Handler/Response/AccountNotFoundHandler.php new file mode 100644 index 0000000..1ee0c5b --- /dev/null +++ b/src/Handler/Response/AccountNotFoundHandler.php @@ -0,0 +1,78 @@ +response->getStatusCode() && + static::isContentType($responseData->response, 'text/html') && + RequestMethod::GET !== strtoupper($responseData->request->getMethod()) + ) { + $errorResponse = new ErrorResponse(); + $errorResponse->errorMsg = 'Account does not exist.'; + + $event = new FailureRequestEvent( + $responseData->baseUrl, + $responseData->request, + $responseData->response, + new AccountDoesNotExistException($errorResponse, $responseData->response->getStatusCode()) + ); + + $this->dispatch($event); + + if (!$event->shouldSuppressThrow()) { + throw $event->getException(); + } + } + + return $this->next($responseData); + } + + /** + * Returns true if response contains provided content type. + * + * @param \Psr\Http\Message\ResponseInterface $response + * @param string $mime + * + * @return bool + */ + private static function isContentType(ResponseInterface $response, string $mime): bool + { + $contentTypeHeader = $response->getHeader('Content-Type'); + + foreach ($contentTypeHeader as $contentType) { + if (false !== stripos($contentType, $mime)) { + return true; + } + } + + return false; + } +} diff --git a/src/Handler/Response/CallbackResponseHandler.php b/src/Handler/Response/CallbackResponseHandler.php new file mode 100644 index 0000000..548fb7d --- /dev/null +++ b/src/Handler/Response/CallbackResponseHandler.php @@ -0,0 +1,48 @@ +callback = $callback; + } + + /** + * @inheritDoc + */ + protected function handleResponse(ResponseData $responseData) + { + call_user_func( + $this->callback, + $responseData, + $this->serializer, + $this->eventDispatcher, + $this->apiExceptionFactory + ); + + return $this->next($responseData); + } +} diff --git a/src/Handler/Response/ErrorResponseHandler.php b/src/Handler/Response/ErrorResponseHandler.php new file mode 100644 index 0000000..3465b99 --- /dev/null +++ b/src/Handler/Response/ErrorResponseHandler.php @@ -0,0 +1,49 @@ +response->getStatusCode() >= 400) { + $event = new FailureRequestEvent( + $responseData->baseUrl, + $responseData->request, + $responseData->response, + $this->apiExceptionFactory->createException( + $this->unmarshalBody($responseData->response, ErrorResponse::class), + $responseData->response->getStatusCode() + ) + ); + + $this->dispatch($event); + + if (!$event->shouldSuppressThrow()) { + throw $event->getException(); + } + } + + return $this->next($responseData); + } +} diff --git a/src/Handler/Response/FilesDownloadResponseHandler.php b/src/Handler/Response/FilesDownloadResponseHandler.php new file mode 100644 index 0000000..c2b52c6 --- /dev/null +++ b/src/Handler/Response/FilesDownloadResponseHandler.php @@ -0,0 +1,93 @@ +request->getUri()->getPath())) { + $this->next($responseData); + + return; + } + + $responseData->responseModel = new FilesDownloadResponse( + static::fileNameFromDisposition($responseData->response->getHeader('Content-Disposition')), + $responseData->response->getBody() + ); + $this->dispatch(new SuccessRequestEvent( + $responseData->baseUrl, + $responseData->request, + $responseData->response, + $responseData->responseModel + )); + } + + /** + * Parses filename from a Content-Disposition header value. + * + * @param string[] $headerValues + * + * @return string + * @link https://tools.ietf.org/html/rfc6266 + * @link https://tools.ietf.org/html/rfc2388 + */ + private static function fileNameFromDisposition(array $headerValues): string + { + $fileName = ''; + + foreach ($headerValues as $value) { + $value = trim($value); + + if (false === strpos($value, ';')) { + continue; + } + + $parts = explode(';', $value, 2)[1]; + $parts = explode(';', $parts); + $attributes = []; + + foreach ($parts as $part) { + if (false === strpos($part, '=')) { + continue; + } + + [$key, $value] = explode('=', $part, 2); + $attributes[trim($key)] = trim($value); + } + + if (empty($attributes['filename'])) { + continue; + } + + $fileName = trim($attributes['filename']); + + if (('' !== $fileName) && '"' === $fileName[0] && $fileName[strlen($fileName) - 1] === '"') { + $fileName = substr($fileName, 1, -1); + } + } + + return $fileName; + } +} diff --git a/src/Handler/Response/UnmarshalResponseHandler.php b/src/Handler/Response/UnmarshalResponseHandler.php new file mode 100644 index 0000000..aae594a --- /dev/null +++ b/src/Handler/Response/UnmarshalResponseHandler.php @@ -0,0 +1,36 @@ +responseModel = $this->unmarshalBody($responseData->response, $responseData->type); + $this->dispatch(new SuccessRequestEvent( + $responseData->baseUrl, + $responseData->request, + $responseData->response, + $responseData->responseModel + )); + } +} diff --git a/src/Interfaces/ApiExceptionFactoryAwareInterface.php b/src/Interfaces/ApiExceptionFactoryAwareInterface.php new file mode 100644 index 0000000..6e09b69 --- /dev/null +++ b/src/Interfaces/ApiExceptionFactoryAwareInterface.php @@ -0,0 +1,26 @@ + + */ + public function encodeArray($object, string $type = ''): array; + + /** + * Returns underlying serializer instance. + * + * @return \Liip\Serializer\SerializerInterface + */ + public function getSerializer(): SerializerInterface; +} diff --git a/src/Interfaces/HandlerInterface.php b/src/Interfaces/HandlerInterface.php new file mode 100644 index 0000000..38d5f4f --- /dev/null +++ b/src/Interfaces/HandlerInterface.php @@ -0,0 +1,62 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("managerId") + */ + public $managerId; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("vip") + */ + public $vip; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("bad") + */ + public $bad; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("browserId") + */ + public $browserId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("site") + */ + public $site; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\CustomerContragent + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Customers\CustomerContragent") + * @JMS\SerializedName("contragent") + */ + public $contragent; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\CustomerTag[] + * + * @JMS\Type("array") + * @JMS\SerializedName("tags") + */ + public $tags; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("avgMarginSumm") + */ + public $avgMarginSumm; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("marginSumm") + */ + public $marginSumm; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("totalSumm") + */ + public $totalSumm; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("averageSumm") + */ + public $averageSumm; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("ordersCount") + */ + public $ordersCount; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("costSumm") + */ + public $costSumm; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("customFields") + */ + public $customFields; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("personalDiscount") + */ + public $personalDiscount; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("cumulativeDiscount") + */ + public $cumulativeDiscount; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("discountCardNumber") + */ + public $discountCardNumber; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("firstClientId") + */ + public $firstClientId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("lastClientId") + */ + public $lastClientId; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\CustomerAddress + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Customers\CustomerAddress") + * @JMS\SerializedName("address") + */ + public $address; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\Segment[] + * + * @JMS\Type("array") + * @JMS\SerializedName("segments") + */ + public $segments; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("maturationTime") + */ + public $maturationTime; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("firstName") + */ + public $firstName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("lastName") + */ + public $lastName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("patronymic") + */ + public $patronymic; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("sex") + */ + public $sex; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("presumableSex") + */ + public $presumableSex; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("email") + */ + public $email; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("emailMarketingUnsubscribedAt") + */ + public $emailMarketingUnsubscribedAt; + + /** + * @var string[] + * + * @JMS\Type("array") + * @JMS\SerializedName("phones") + */ + public $phones; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("birthday") + */ + public $birthday; + + /** + * @var \RetailCrm\Api\Model\Entity\Source + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Source") + * @JMS\SerializedName("source") + */ + public $source; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("photoUrl") + */ + public $photoUrl; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("mgCustomerId") + */ + public $mgCustomerId; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("subscribed") + */ + public $subscribed; +} diff --git a/src/Model/Callback/Entity/Delivery/DeliveryAddress.php b/src/Model/Callback/Entity/Delivery/DeliveryAddress.php new file mode 100644 index 0000000..a035786 --- /dev/null +++ b/src/Model/Callback/Entity/Delivery/DeliveryAddress.php @@ -0,0 +1,189 @@ +") + * @JMS\SerializedName("items") + */ + public $items; +} diff --git a/src/Model/Callback/Entity/Delivery/PackageItem.php b/src/Model/Callback/Entity/Delivery/PackageItem.php new file mode 100644 index 0000000..e033c29 --- /dev/null +++ b/src/Model/Callback/Entity/Delivery/PackageItem.php @@ -0,0 +1,125 @@ +") + * @JMS\SerializedName("markingCodes") + */ + public $markingCodes; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("properties") + */ + public $properties; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("weight") + */ + public $weight; +} diff --git a/src/Model/Callback/Entity/Delivery/PaymentType.php b/src/Model/Callback/Entity/Delivery/PaymentType.php new file mode 100644 index 0000000..f1427ff --- /dev/null +++ b/src/Model/Callback/Entity/Delivery/PaymentType.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("packages") + */ + public $packages; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("payerType") + */ + public $payerType; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("declaredValue") + */ + public $declaredValue; + + /** + * @var \RetailCrm\Api\Model\Callback\Entity\Delivery\DeliveryAddress + * + * @JMS\Type("RetailCrm\Api\Model\Callback\Entity\Delivery\DeliveryAddress") + * @JMS\SerializedName("shipmentAddress") + */ + public $shipmentAddress; + + /** + * @var \RetailCrm\Api\Model\Callback\Entity\Delivery\DeliveryAddress + * + * @JMS\Type("RetailCrm\Api\Model\Callback\Entity\Delivery\DeliveryAddress") + * @JMS\SerializedName("deliveryAddress") + */ + public $deliveryAddress; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("cod") + */ + public $cod; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("shipmentDate") + */ + public $shipmentDate; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("deliveryDate") + */ + public $deliveryDate; + + /** + * @var \RetailCrm\Api\Model\Entity\Delivery\TimeInterval + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Delivery\TimeInterval") + * @JMS\SerializedName("deliveryTime") + */ + public $deliveryTime; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("currency") + */ + public $currency; + + /** + * @var mixed[][] + * + * @JMS\Type("array") + * @JMS\SerializedName("extraData") + */ + public $extraData; +} diff --git a/src/Model/Callback/Entity/Delivery/RequestProperty/RequestDelete.php b/src/Model/Callback/Entity/Delivery/RequestProperty/RequestDelete.php new file mode 100644 index 0000000..dbf37dd --- /dev/null +++ b/src/Model/Callback/Entity/Delivery/RequestProperty/RequestDelete.php @@ -0,0 +1,29 @@ +") + * @JMS\SerializedName("deliveryIds") + */ + public $deliveryIds; +} diff --git a/src/Model/Callback/Entity/Delivery/RequestProperty/RequestSave.php b/src/Model/Callback/Entity/Delivery/RequestProperty/RequestSave.php new file mode 100644 index 0000000..3411cb9 --- /dev/null +++ b/src/Model/Callback/Entity/Delivery/RequestProperty/RequestSave.php @@ -0,0 +1,125 @@ +") + * @JMS\SerializedName("packages") + */ + public $packages; + + /** + * @var \RetailCrm\Api\Model\Callback\Entity\Delivery\SaveDeliveryData + * + * @JMS\Type("RetailCrm\Api\Model\Callback\Entity\Delivery\SaveDeliveryData") + * @JMS\SerializedName("delivery") + */ + public $delivery; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("currency") + */ + public $currency; +} diff --git a/src/Model/Callback/Entity/Delivery/RequestProperty/RequestShipmentDelete.php b/src/Model/Callback/Entity/Delivery/RequestProperty/RequestShipmentDelete.php new file mode 100644 index 0000000..546619e --- /dev/null +++ b/src/Model/Callback/Entity/Delivery/RequestProperty/RequestShipmentDelete.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("date") + */ + public $date; + + /** + * @var \RetailCrm\Api\Model\Entity\Delivery\TimeInterval + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Delivery\TimeInterval") + * @JMS\SerializedName("time") + */ + public $time; + + /** + * @var \RetailCrm\Api\Model\Callback\Entity\Delivery\DeliveryAddress + * + * @JMS\Type("RetailCrm\Api\Model\Callback\Entity\Delivery\DeliveryAddress") + * @JMS\SerializedName("address") + */ + public $address; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("store") + */ + public $store; + + /** + * @var \RetailCrm\Api\Model\Callback\Entity\Delivery\ShipmentOrder[] + * + * @JMS\Type("array") + * @JMS\SerializedName("orders") + */ + public $orders; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("comment") + */ + public $comment; + + /** + * @var mixed[][] + * + * @JMS\Type("array") + * @JMS\SerializedName("extraData") + */ + public $extraData; +} diff --git a/src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseAutocompleteItem.php b/src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseAutocompleteItem.php new file mode 100644 index 0000000..5170bf8 --- /dev/null +++ b/src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseAutocompleteItem.php @@ -0,0 +1,45 @@ +") + * @JMS\SerializedName("pickuppointList") + */ + public $pickuppointList; +} diff --git a/src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseLoadDeliveryData.php b/src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseLoadDeliveryData.php new file mode 100644 index 0000000..5d118c6 --- /dev/null +++ b/src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseLoadDeliveryData.php @@ -0,0 +1,118 @@ +") + * @JMS\SerializedName("shipmentDate") + */ + public $shipmentDate; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("deliveryDate") + */ + public $deliveryDate; + + /** + * @var \RetailCrm\Api\Model\Entity\Delivery\TimeInterval + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Delivery\TimeInterval") + * @JMS\SerializedName("deliveryTime") + */ + public $deliveryTime; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("tariff") + */ + public $tariff; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("tariffName") + */ + public $tariffName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("payerType") + */ + public $payerType; + + /** + * @var \RetailCrm\Api\Model\Entity\Delivery\StatusInfo + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Delivery\StatusInfo") + * @JMS\SerializedName("status") + */ + public $status; + + /** + * @var mixed[] + * + * @JMS\Type("array") + * @JMS\SerializedName("extraData") + */ + public $extraData; + + /** + * @var \RetailCrm\Api\Model\Callback\Entity\Delivery\DeliveryAddress + * + * @JMS\Type("RetailCrm\Api\Model\Callback\Entity\Delivery\DeliveryAddress") + * @JMS\SerializedName("shipmentAddress") + */ + public $shipmentAddress; + + /** + * @var \RetailCrm\Api\Model\Callback\Entity\Delivery\DeliveryAddress + * + * @JMS\Type("RetailCrm\Api\Model\Callback\Entity\Delivery\DeliveryAddress") + * @JMS\SerializedName("deliveryAddress") + */ + public $deliveryAddress; +} diff --git a/src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseSave.php b/src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseSave.php new file mode 100644 index 0000000..adbc7e3 --- /dev/null +++ b/src/Model/Callback/Entity/Delivery/ResponseProperty/ResponseSave.php @@ -0,0 +1,61 @@ +") + * @JMS\SerializedName("shipmentDate") + */ + public $shipmentDate; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("deliveryDate") + */ + public $deliveryDate; + + /** + * @var \RetailCrm\Api\Model\Entity\Delivery\TimeInterval + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Delivery\TimeInterval") + * @JMS\SerializedName("deliveryTime") + */ + public $deliveryTime; + + /** + * @var mixed[][] + * + * @JMS\Type("array") + * @JMS\SerializedName("extraData") + */ + public $extraData; +} diff --git a/src/Model/Callback/Entity/Delivery/SerializedStoreWeekOpeningHours.php b/src/Model/Callback/Entity/Delivery/SerializedStoreWeekOpeningHours.php new file mode 100644 index 0000000..44e6986 --- /dev/null +++ b/src/Model/Callback/Entity/Delivery/SerializedStoreWeekOpeningHours.php @@ -0,0 +1,126 @@ +") + * @JMS\SerializedName("mo") + */ + public $mo; + + /** + * @var \RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime[] + * + * @JMS\Type("array") + * @JMS\SerializedName("tu") + */ + public $tu; + + /** + * @var \RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime[] + * + * @JMS\Type("array") + * @JMS\SerializedName("we") + */ + public $we; + + /** + * @var \RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime[] + * + * @JMS\Type("array") + * @JMS\SerializedName("th") + */ + public $th; + + /** + * @var \RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime[] + * + * @JMS\Type("array") + * @JMS\SerializedName("fr") + */ + public $fr; + + /** + * @var \RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime[] + * + * @JMS\Type("array") + * @JMS\SerializedName("sa") + */ + public $sa; + + /** + * @var \RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime[] + * + * @JMS\Type("array") + * @JMS\SerializedName("su") + */ + public $su; + + /** + * SerializedStoreWeekOpeningHours constructor. + * + * @param \RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime[] $monday + * @param \RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime[] $tuesday + * @param \RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime[] $wednesday + * @param \RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime[] $thursday + * @param \RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime[] $friday + * @param \RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime[] $saturday + * @param \RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime[] $sunday + */ + public function __construct( + ?array $monday = null, + ?array $tuesday = null, + ?array $wednesday = null, + ?array $thursday = null, + ?array $friday = null, + ?array $saturday = null, + ?array $sunday = null + ) { + if (null !== $monday) { + $this->mo = $monday; + } + + if (null !== $tuesday) { + $this->tu = $tuesday; + } + + if (null !== $wednesday) { + $this->we = $wednesday; + } + + if (null !== $thursday) { + $this->th = $thursday; + } + + if (null !== $friday) { + $this->fr = $friday; + } + + if (null !== $saturday) { + $this->sa = $saturday; + } + + if (null !== $sunday) { + $this->su = $sunday; + } + } +} diff --git a/src/Model/Callback/Entity/Delivery/ShipmentOrder.php b/src/Model/Callback/Entity/Delivery/ShipmentOrder.php new file mode 100644 index 0000000..843ff58 --- /dev/null +++ b/src/Model/Callback/Entity/Delivery/ShipmentOrder.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("packages") + */ + public $packages; +} diff --git a/src/Model/Callback/Entity/Delivery/Store.php b/src/Model/Callback/Entity/Delivery/Store.php new file mode 100644 index 0000000..ad0765e --- /dev/null +++ b/src/Model/Callback/Entity/Delivery/Store.php @@ -0,0 +1,37 @@ +startTime = $startTime; + } + + if ('' !== $endTime) { + $this->endTime = $endTime; + } + + if ('' !== $lunchStartTime) { + $this->lunchStartTime = $lunchStartTime; + } + + if ('' !== $lunchEndTime) { + $this->lunchEndTime = $lunchEndTime; + } + } +} diff --git a/src/Model/Callback/Entity/Delivery/Tariff.php b/src/Model/Callback/Entity/Delivery/Tariff.php new file mode 100644 index 0000000..64c601c --- /dev/null +++ b/src/Model/Callback/Entity/Delivery/Tariff.php @@ -0,0 +1,53 @@ +") + * @JMS\SerializedName("offers") + */ + public $offers; +} diff --git a/src/Model/Callback/Entity/Store/OrderDataModel.php b/src/Model/Callback/Entity/Store/OrderDataModel.php new file mode 100644 index 0000000..45b7f96 --- /dev/null +++ b/src/Model/Callback/Entity/Store/OrderDataModel.php @@ -0,0 +1,45 @@ +") + * @JMS\SerializedName("shipmentDate") + */ + public $shipmentDate; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("invoiceNumber") + */ + public $invoiceNumber; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("deliveryNoteNumber") + */ + public $deliveryNoteNumber; +} diff --git a/src/Model/Callback/Entity/Store/PackItemModel.php b/src/Model/Callback/Entity/Store/PackItemModel.php new file mode 100644 index 0000000..8168ffa --- /dev/null +++ b/src/Model/Callback/Entity/Store/PackItemModel.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("externalIds") + */ + public $externalIds; +} diff --git a/src/Model/Callback/Enum/Recommendations/Mode.php b/src/Model/Callback/Enum/Recommendations/Mode.php new file mode 100644 index 0000000..d9ad2d9 --- /dev/null +++ b/src/Model/Callback/Enum/Recommendations/Mode.php @@ -0,0 +1,22 @@ +") + * @JMS\SerializedName("result") + */ + public $result; +} diff --git a/src/Model/Callback/Response/Delivery/GetResponse.php b/src/Model/Callback/Response/Delivery/GetResponse.php new file mode 100644 index 0000000..1befad4 --- /dev/null +++ b/src/Model/Callback/Response/Delivery/GetResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("result") + */ + public $result; +} diff --git a/src/Model/Callback/Response/Delivery/ShipmentSaveResponse.php b/src/Model/Callback/Response/Delivery/ShipmentSaveResponse.php new file mode 100644 index 0000000..86a573e --- /dev/null +++ b/src/Model/Callback/Response/Delivery/ShipmentSaveResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("result") + */ + public $result; +} diff --git a/src/Model/Callback/Response/ErrorResponse.php b/src/Model/Callback/Response/ErrorResponse.php new file mode 100644 index 0000000..af99fea --- /dev/null +++ b/src/Model/Callback/Response/ErrorResponse.php @@ -0,0 +1,22 @@ +") + * @JMS\SerializedName("ids") + */ + public $ids; +} diff --git a/src/Model/Callback/Response/Store/InventoriesUploadResponse.php b/src/Model/Callback/Response/Store/InventoriesUploadResponse.php new file mode 100644 index 0000000..324b392 --- /dev/null +++ b/src/Model/Callback/Response/Store/InventoriesUploadResponse.php @@ -0,0 +1,45 @@ +") + * @JMS\SerializedName("offers") + */ + public $offers; +} diff --git a/src/Model/Callback/Response/Store/ReserveResponse.php b/src/Model/Callback/Response/Store/ReserveResponse.php new file mode 100644 index 0000000..45f43e2 --- /dev/null +++ b/src/Model/Callback/Response/Store/ReserveResponse.php @@ -0,0 +1,45 @@ +") + * @JMS\SerializedName("inventories") + */ + public $inventories; +} diff --git a/src/Model/Entity/CodeValueModel.php b/src/Model/Entity/CodeValueModel.php new file mode 100644 index 0000000..fd8e4cd --- /dev/null +++ b/src/Model/Entity/CodeValueModel.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("dateFrom") + */ + public $dateFrom; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d'>") + * @JMS\SerializedName("dateTo") + */ + public $dateTo; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("summ") + */ + public $summ; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("costItem") + */ + public $costItem; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("comment") + */ + public $comment; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("createdBy") + */ + public $createdBy; + + /** + * @var \RetailCrm\Api\Model\Entity\Costs\CostOrder + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Costs\CostOrder") + * @JMS\SerializedName("order") + */ + public $order; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("userId") + */ + public $userId; + + /** + * @var string[] + * + * @JMS\Type("array") + * @JMS\SerializedName("sites") + */ + public $sites; +} diff --git a/src/Model/Entity/Costs/CostOrder.php b/src/Model/Entity/Costs/CostOrder.php new file mode 100644 index 0000000..7f3d087 --- /dev/null +++ b/src/Model/Entity/Costs/CostOrder.php @@ -0,0 +1,45 @@ +") + * @JMS\SerializedName("elements") + */ + public $elements; +} diff --git a/src/Model/Entity/CustomFields/CustomField.php b/src/Model/Entity/CustomFields/CustomField.php new file mode 100644 index 0000000..08b69d2 --- /dev/null +++ b/src/Model/Entity/CustomFields/CustomField.php @@ -0,0 +1,125 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("managerId") + */ + public $managerId; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("vip") + */ + public $vip; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("bad") + */ + public $bad; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("browserId") + */ + public $browserId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("site") + */ + public $site; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\CustomerContragent + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Customers\CustomerContragent") + * @JMS\SerializedName("contragent") + */ + public $contragent; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\CustomerTag[] + * + * @JMS\Type("array") + * @JMS\SerializedName("tags") + */ + public $tags; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("avgMarginSumm") + */ + public $avgMarginSumm; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("marginSumm") + */ + public $marginSumm; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("totalSumm") + */ + public $totalSumm; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("averageSumm") + */ + public $averageSumm; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("ordersCount") + */ + public $ordersCount; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("costSumm") + */ + public $costSumm; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("customFields") + */ + public $customFields; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("personalDiscount") + */ + public $personalDiscount; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("cumulativeDiscount") + */ + public $cumulativeDiscount; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("discountCardNumber") + */ + public $discountCardNumber; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("firstClientId") + */ + public $firstClientId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("lastClientId") + */ + public $lastClientId; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\CustomerAddress + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Customers\CustomerAddress") + * @JMS\SerializedName("address") + */ + public $address; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\Segment[] + * + * @JMS\Type("array") + * @JMS\SerializedName("segments") + */ + public $segments; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("maturationTime") + */ + public $maturationTime; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("firstName") + */ + public $firstName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("lastName") + */ + public $lastName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("patronymic") + */ + public $patronymic; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("sex") + */ + public $sex; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("presumableSex") + */ + public $presumableSex; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("email") + */ + public $email; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("emailMarketingUnsubscribedAt") + */ + public $emailMarketingUnsubscribedAt; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\CustomerPhone[] + * + * @JMS\Type("array") + * @JMS\SerializedName("phones") + */ + public $phones; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("birthday") + */ + public $birthday; + + /** + * @var \RetailCrm\Api\Model\Entity\Source + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Source") + * @JMS\SerializedName("source") + */ + public $source; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("photoUrl") + */ + public $photoUrl; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("mgCustomerId") + */ + public $mgCustomerId; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("subscribed") + */ + public $subscribed; +} diff --git a/src/Model/Entity/Customers/CustomerAddress.php b/src/Model/Entity/Customers/CustomerAddress.php new file mode 100644 index 0000000..1f8c4a2 --- /dev/null +++ b/src/Model/Entity/Customers/CustomerAddress.php @@ -0,0 +1,205 @@ +") + * @JMS\SerializedName("certificateDate") + */ + public $certificateDate; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("BIK") + */ + public $BIK; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("bank") + */ + public $bank; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("bankAddress") + */ + public $bankAddress; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("corrAccount") + */ + public $corrAccount; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("bankAccount") + */ + public $bankAccount; +} diff --git a/src/Model/Entity/Customers/CustomerHistory.php b/src/Model/Entity/Customers/CustomerHistory.php new file mode 100644 index 0000000..c51f0dd --- /dev/null +++ b/src/Model/Entity/Customers/CustomerHistory.php @@ -0,0 +1,126 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("created") + */ + public $created; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("deleted") + */ + public $deleted; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("source") + */ + public $source; + + /** + * @var \RetailCrm\Api\Model\Entity\HistoryUser + * + * @JMS\Type("RetailCrm\Api\Model\Entity\HistoryUser") + * @JMS\SerializedName("user") + */ + public $user; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("field") + */ + public $field; + + /** + * @var mixed + * + * @JMS\Type("mixed") + * @JMS\SerializedName("oldValue") + */ + public $oldValue; + + /** + * @var mixed + * + * @JMS\Type("mixed") + * @JMS\SerializedName("newValue") + */ + public $newValue; + + /** + * @var \RetailCrm\Api\Model\Entity\HistoryApiKey + * + * @JMS\Type("RetailCrm\Api\Model\Entity\HistoryApiKey") + * @JMS\SerializedName("apiKey") + */ + public $apiKey; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\Customer + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Customers\Customer") + * @JMS\SerializedName("customer") + */ + public $customer; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\HistoryAddress + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Customers\HistoryAddress") + * @JMS\SerializedName("address") + */ + public $address; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\Customer + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Customers\Customer") + * @JMS\SerializedName("combinedTo") + */ + public $combinedTo; +} diff --git a/src/Model/Entity/Customers/CustomerNote.php b/src/Model/Entity/Customers/CustomerNote.php new file mode 100644 index 0000000..ad0dc64 --- /dev/null +++ b/src/Model/Entity/Customers/CustomerNote.php @@ -0,0 +1,62 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; +} diff --git a/src/Model/Entity/Customers/CustomerPhone.php b/src/Model/Entity/Customers/CustomerPhone.php new file mode 100644 index 0000000..50fc45e --- /dev/null +++ b/src/Model/Entity/Customers/CustomerPhone.php @@ -0,0 +1,41 @@ +number = $number; + } + } +} diff --git a/src/Model/Entity/Customers/CustomerTag.php b/src/Model/Entity/Customers/CustomerTag.php new file mode 100644 index 0000000..8e7e953 --- /dev/null +++ b/src/Model/Entity/Customers/CustomerTag.php @@ -0,0 +1,57 @@ +name = $name; + } + } +} diff --git a/src/Model/Entity/Customers/HistoryAddress.php b/src/Model/Entity/Customers/HistoryAddress.php new file mode 100644 index 0000000..135f5a1 --- /dev/null +++ b/src/Model/Entity/Customers/HistoryAddress.php @@ -0,0 +1,53 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("isDynamic") + */ + public $isDynamic; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("customersCount") + */ + public $customersCount; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("active") + */ + public $active; +} diff --git a/src/Model/Entity/Customers/SerializedCustomerReference.php b/src/Model/Entity/Customers/SerializedCustomerReference.php new file mode 100644 index 0000000..af5eda1 --- /dev/null +++ b/src/Model/Entity/Customers/SerializedCustomerReference.php @@ -0,0 +1,39 @@ +id = $id; + } +} diff --git a/src/Model/Entity/CustomersCorporate/Company.php b/src/Model/Entity/CustomersCorporate/Company.php new file mode 100644 index 0000000..17806b7 --- /dev/null +++ b/src/Model/Entity/CustomersCorporate/Company.php @@ -0,0 +1,158 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var \RetailCrm\Api\Model\Entity\CustomersCorporate\SerializedEntityCustomer + * + * @JMS\Type("RetailCrm\Api\Model\Entity\CustomersCorporate\SerializedEntityCustomer") + * @JMS\SerializedName("customer") + */ + public $customer; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\CustomerContragent + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Customers\CustomerContragent") + * @JMS\SerializedName("contragent") + */ + public $contragent; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\CustomerAddress + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Customers\CustomerAddress") + * @JMS\SerializedName("address") + */ + public $address; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("customFields") + */ + public $customFields; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("marginSumm") + */ + public $marginSumm; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("totalSumm") + */ + public $totalSumm; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("averageSumm") + */ + public $averageSumm; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("ordersCount") + */ + public $ordersCount; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("costSumm") + */ + public $costSumm; +} diff --git a/src/Model/Entity/CustomersCorporate/CustomerContact.php b/src/Model/Entity/CustomersCorporate/CustomerContact.php new file mode 100644 index 0000000..572b9a5 --- /dev/null +++ b/src/Model/Entity/CustomersCorporate/CustomerContact.php @@ -0,0 +1,53 @@ +") + * @JMS\SerializedName("companies") + */ + public $companies; +} diff --git a/src/Model/Entity/CustomersCorporate/CustomerContactCompany.php b/src/Model/Entity/CustomersCorporate/CustomerContactCompany.php new file mode 100644 index 0000000..410976b --- /dev/null +++ b/src/Model/Entity/CustomersCorporate/CustomerContactCompany.php @@ -0,0 +1,29 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("managerId") + */ + public $managerId; + + /** + * @var \RetailCrm\Api\Model\Entity\Source + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Source") + * @JMS\SerializedName("source") + */ + public $source; + + /** + * @var \RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerContact[] + * + * @JMS\Type("array") + * @JMS\SerializedName("customerContacts") + */ + public $customerContacts; + + /** + * @var \RetailCrm\Api\Model\Entity\CustomersCorporate\Company[] + * + * @JMS\Type("array") + * @JMS\SerializedName("companies") + */ + public $companies; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\CustomerAddress[] + * + * @JMS\Type("array") + * @JMS\SerializedName("addresses") + */ + public $addresses; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("vip") + */ + public $vip; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("bad") + */ + public $bad; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("site") + */ + public $site; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\CustomerTag[] + * + * @JMS\Type("array") + * @JMS\SerializedName("tags") + */ + public $tags; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("avgMarginSumm") + */ + public $avgMarginSumm; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("marginSumm") + */ + public $marginSumm; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("totalSumm") + */ + public $totalSumm; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("averageSumm") + */ + public $averageSumm; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("ordersCount") + */ + public $ordersCount; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("costSumm") + */ + public $costSumm; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("customFields") + */ + public $customFields; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("personalDiscount") + */ + public $personalDiscount; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("cumulativeDiscount") + */ + public $cumulativeDiscount; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("discountCardNumber") + */ + public $discountCardNumber; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("firstClientId") + */ + public $firstClientId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("lastClientId") + */ + public $lastClientId; + + /** + * @var \RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerContact + * + * @JMS\Type("RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerContact") + * @JMS\SerializedName("mainCustomerContact") + */ + public $mainCustomerContact; + + /** + * @var \RetailCrm\Api\Model\Entity\CustomersCorporate\EntityWithExternalIdNameOutput + * + * @JMS\Type("RetailCrm\Api\Model\Entity\CustomersCorporate\EntityWithExternalIdNameOutput") + * @JMS\SerializedName("mainCompany") + */ + public $mainCompany; +} diff --git a/src/Model/Entity/CustomersCorporate/CustomerCorporateHistory.php b/src/Model/Entity/CustomersCorporate/CustomerCorporateHistory.php new file mode 100644 index 0000000..38d10a4 --- /dev/null +++ b/src/Model/Entity/CustomersCorporate/CustomerCorporateHistory.php @@ -0,0 +1,142 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("created") + */ + public $created; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("deleted") + */ + public $deleted; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("source") + */ + public $source; + + /** + * @var \RetailCrm\Api\Model\Entity\HistoryUser + * + * @JMS\Type("RetailCrm\Api\Model\Entity\HistoryUser") + * @JMS\SerializedName("user") + */ + public $user; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("field") + */ + public $field; + + /** + * @var mixed + * + * @JMS\Type("mixed") + * @JMS\SerializedName("oldValue") + */ + public $oldValue; + + /** + * @var mixed + * + * @JMS\Type("mixed") + * @JMS\SerializedName("newValue") + */ + public $newValue; + + /** + * @var \RetailCrm\Api\Model\Entity\HistoryApiKey + * + * @JMS\Type("RetailCrm\Api\Model\Entity\HistoryApiKey") + * @JMS\SerializedName("apiKey") + */ + public $apiKey; + + /** + * @var \RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate + * + * @JMS\Type("RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate") + * @JMS\SerializedName("customer") + */ + public $customer; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\CustomerAddress + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Customers\CustomerAddress") + * @JMS\SerializedName("address") + */ + public $address; + + /** + * @var \RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate + * + * @JMS\Type("RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate") + * @JMS\SerializedName("combinedTo") + */ + public $combinedTo; + + /** + * @var \RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerContact + * + * @JMS\Type("RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerContact") + * @JMS\SerializedName("customerContact") + */ + public $customerContact; + + /** + * @var \RetailCrm\Api\Model\Entity\CustomersCorporate\Company + * + * @JMS\Type("RetailCrm\Api\Model\Entity\CustomersCorporate\Company") + * @JMS\SerializedName("company") + */ + public $company; +} diff --git a/src/Model/Entity/CustomersCorporate/EntityWithExternalIdInput.php b/src/Model/Entity/CustomersCorporate/EntityWithExternalIdInput.php new file mode 100644 index 0000000..84affc4 --- /dev/null +++ b/src/Model/Entity/CustomersCorporate/EntityWithExternalIdInput.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("companies") + */ + public $companies; + + /** + * Returns customer with provided ID. + * + * @param int $id + * + * @return self + */ + public static function withId(int $id): self + { + $customer = new self(); + $customer->id = $id; + + return $customer; + } + + /** + * Returns customer with provided external ID and site. + * + * @param string $externalId + * @param string $site + * + * @return self + */ + public static function withExternalId(string $externalId, string $site): self + { + $customer = new self(); + $customer->externalId = $externalId; + $customer->site = $site; + + return $customer; + } +} diff --git a/src/Model/Entity/Delivery/DeliveryCalculation.php b/src/Model/Entity/Delivery/DeliveryCalculation.php new file mode 100644 index 0000000..6ff5b2f --- /dev/null +++ b/src/Model/Entity/Delivery/DeliveryCalculation.php @@ -0,0 +1,53 @@ +") + * @JMS\SerializedName("date") + */ + public $date; + + /** + * @var \RetailCrm\Api\Model\Entity\Delivery\TimeInterval + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Delivery\TimeInterval") + * @JMS\SerializedName("time") + */ + public $time; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("comment") + */ + public $comment; + + /** + * @var \RetailCrm\Api\Model\Entity\Delivery\SerializedEntityOrder[] + * + * @JMS\Type("array") + * @JMS\SerializedName("orders") + */ + public $orders; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("extraData") + */ + public $extraData; + + /** + * @var \RetailCrm\Api\Model\Entity\Delivery\TimeInterval + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Delivery\TimeInterval") + * @JMS\SerializedName("lunchTime") + */ + public $lunchTime; +} diff --git a/src/Model/Entity/Delivery/RequestStatusUpdateItem.php b/src/Model/Entity/Delivery/RequestStatusUpdateItem.php new file mode 100644 index 0000000..45c761a --- /dev/null +++ b/src/Model/Entity/Delivery/RequestStatusUpdateItem.php @@ -0,0 +1,61 @@ +") + * @JMS\SerializedName("history") + */ + public $history; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("trackNumber") + */ + public $trackNumber; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("cost") + */ + public $cost; + + /** + * @var string[] + * + * @JMS\Type("array") + * @JMS\SerializedName("extraData") + */ + public $extraData; +} diff --git a/src/Model/Entity/Delivery/SerializedEntityOrder.php b/src/Model/Entity/Delivery/SerializedEntityOrder.php new file mode 100644 index 0000000..7ffbf97 --- /dev/null +++ b/src/Model/Entity/Delivery/SerializedEntityOrder.php @@ -0,0 +1,104 @@ +id = $id; + } + + if ('' !== $externalId) { + $this->externalId = $externalId; + } + + if ('' !== $number) { + $this->number = $number; + } + } + + /** + * Returns this entity with specified ID + * + * @param int $id + * + * @return self + */ + public static function withId(int $id): self + { + return new self($id, '', ''); + } + + + /** + * Returns this entity with specified external ID + * + * @param string $externalId + * + * @return self + */ + public static function withExternalId(string $externalId): self + { + return new self(0, $externalId, ''); + } + + /** + * Returns this entity with specified order number + * + * @param string $number + * + * @return self + */ + public static function withNumber(string $number): self + { + return new self(0, '', $number); + } +} diff --git a/src/Model/Entity/Delivery/SerializedOrder.php b/src/Model/Entity/Delivery/SerializedOrder.php new file mode 100644 index 0000000..d9d52e9 --- /dev/null +++ b/src/Model/Entity/Delivery/SerializedOrder.php @@ -0,0 +1,69 @@ +") + * @JMS\SerializedName("items") + */ + public $items; + + /** + * @var \RetailCrm\Api\Model\Entity\Delivery\SerializedOrderDelivery + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Delivery\SerializedOrderDelivery") + * @JMS\SerializedName("delivery") + */ + public $delivery; +} diff --git a/src/Model/Entity/Delivery/SerializedOrderDelivery.php b/src/Model/Entity/Delivery/SerializedOrderDelivery.php new file mode 100644 index 0000000..d323a54 --- /dev/null +++ b/src/Model/Entity/Delivery/SerializedOrderDelivery.php @@ -0,0 +1,46 @@ +") + * @JMS\SerializedName("date") + */ + public $date; + + /** + * @var \RetailCrm\Api\Model\Entity\Delivery\TimeInterval + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Delivery\TimeInterval") + * @JMS\SerializedName("time") + */ + public $time; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Delivery\OrderDeliveryAddress + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\Delivery\OrderDeliveryAddress") + * @JMS\SerializedName("address") + */ + public $address; +} diff --git a/src/Model/Entity/Delivery/SerializedOrderProduct.php b/src/Model/Entity/Delivery/SerializedOrderProduct.php new file mode 100644 index 0000000..9d8a5dc --- /dev/null +++ b/src/Model/Entity/Delivery/SerializedOrderProduct.php @@ -0,0 +1,71 @@ +") + * @JMS\SerializedName("updatedAt") + */ + public $updatedAt; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("comment") + */ + public $comment; +} diff --git a/src/Model/Entity/Delivery/TimeInterval.php b/src/Model/Entity/Delivery/TimeInterval.php new file mode 100644 index 0000000..f139458 --- /dev/null +++ b/src/Model/Entity/Delivery/TimeInterval.php @@ -0,0 +1,115 @@ +") + * @JMS\SerializedName("from") + */ + public $from; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'H:i'>") + * @JMS\SerializedName("to") + */ + public $to; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("custom") + */ + public $custom; + + /** + * TimeInterval constructor. + * + * @param \DateTime|null $from + * @param \DateTime|null $to + */ + public function __construct(?DateTime $from = null, ?DateTime $to = null) + { + if (null !== $from) { + $this->from = $from; + } + + if (null !== $to) { + $this->to = $to; + } + } + + /** + * Creates a new instance of TimeInterval with provided time boundaries. + * Time must be in "H:i" format. If time cannot be parsed, then RuntimeException will be thrown. + * + * @param string $from + * @param string $to + * + * @return \RetailCrm\Api\Model\Entity\Delivery\TimeInterval + */ + public static function withTextInterval(string $from, string $to): TimeInterval + { + $interval = new TimeInterval(); + $interval->from = static::createTime($from); + $interval->to = static::createTime($to); + + return $interval; + } + + /** + * Returns TimeInterval with provided custom interval. + * + * @param string $customInterval + * + * @return TimeInterval + */ + public static function withCustomInterval(string $customInterval): TimeInterval + { + $interval = new TimeInterval(); + $interval->custom = $customInterval; + + return $interval; + } + + /** + * Parses string into DateTime using "H:i" format. Throws RuntimeException on failure. + * + * @param string $time + * + * @return \DateTime + */ + private static function createTime(string $time): DateTime + { + $result = DateTime::createFromFormat('H:i', $time); + + if (false === $result) { + throw new RuntimeException(sprintf('Cannot parse provided time "%s" using "H:i" format.', $time)); + } + + return $result; + } +} diff --git a/src/Model/Entity/Files/Attachment.php b/src/Model/Entity/Files/Attachment.php new file mode 100644 index 0000000..09e0414 --- /dev/null +++ b/src/Model/Entity/Files/Attachment.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("size") + */ + public $size; + + /** + * @var \RetailCrm\Api\Model\Entity\Files\Attachment[] + * + * @JMS\Type("array") + * @JMS\SerializedName("attachment") + */ + public $attachment; +} diff --git a/src/Model/Entity/FixExternalRow.php b/src/Model/Entity/FixExternalRow.php new file mode 100644 index 0000000..4eb896d --- /dev/null +++ b/src/Model/Entity/FixExternalRow.php @@ -0,0 +1,54 @@ +id = $id; + } + + if ('' !== $externalId) { + $this->externalId = $externalId; + } + } +} diff --git a/src/Model/Entity/HistoryApiKey.php b/src/Model/Entity/HistoryApiKey.php new file mode 100644 index 0000000..9a6dd51 --- /dev/null +++ b/src/Model/Entity/HistoryApiKey.php @@ -0,0 +1,29 @@ + + * + * @JMS\Type("array") + * @JMS\SerializedName("actions") + */ + public $actions; + + /** + * @var string[] + * + * @JMS\Type("array") + * @JMS\SerializedName("payerType") + */ + public $payerType; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("platePrintLimit") + */ + public $platePrintLimit; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("rateDeliveryCost") + */ + public $rateDeliveryCost; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("allowPackages") + */ + public $allowPackages; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("codAvailable") + */ + public $codAvailable; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("selfShipmentAvailable") + */ + public $selfShipmentAvailable; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("duplicateOrderProductSupported") + */ + public $duplicateOrderProductSupported; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("allowTrackNumber") + */ + public $allowTrackNumber; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("availableCountries") + */ + public $availableCountries; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("requiredFields") + */ + public $requiredFields; + + /** + * @var \RetailCrm\Api\Model\Entity\Integration\Delivery\Status[] + * + * @JMS\Type("array") + * @JMS\SerializedName("statusList") + */ + public $statusList; + + /** + * @var \RetailCrm\Api\Model\Entity\Integration\Delivery\Plate[] + * + * @JMS\Type("array") + * @JMS\SerializedName("plateList") + */ + public $plateList; + + /** + * @var \RetailCrm\Api\Model\Entity\Integration\Delivery\DeliveryDataField[] + * + * @JMS\Type("array") + * @JMS\SerializedName("deliveryDataFieldList") + */ + public $deliveryDataFieldList; + + /** + * @var \RetailCrm\Api\Model\Entity\Integration\Delivery\DeliveryDataField[] + * + * @JMS\Type("array") + * @JMS\SerializedName("shipmentDataFieldList") + */ + public $shipmentDataFieldList; + + /** + * @var \RetailCrm\Api\Model\Entity\Integration\Delivery\Settings + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Integration\Delivery\Settings") + * @JMS\SerializedName("settings") + */ + public $settings; +} diff --git a/src/Model/Entity/Integration/Delivery/DeliveryDataField.php b/src/Model/Entity/Integration/Delivery/DeliveryDataField.php new file mode 100644 index 0000000..7b6dd50 --- /dev/null +++ b/src/Model/Entity/Integration/Delivery/DeliveryDataField.php @@ -0,0 +1,109 @@ + + * + * @JMS\Type("array") + * @JMS\SerializedName("choices") + */ + public $choices; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("autocompleteUrl") + */ + public $autocompleteUrl; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("visible") + */ + public $visible; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("required") + */ + public $required; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("affectsCost") + */ + public $affectsCost; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("editable") + */ + public $editable; +} diff --git a/src/Model/Entity/Integration/Delivery/PaymentType.php b/src/Model/Entity/Integration/Delivery/PaymentType.php new file mode 100644 index 0000000..0ec6669 --- /dev/null +++ b/src/Model/Entity/Integration/Delivery/PaymentType.php @@ -0,0 +1,45 @@ +") + * @JMS\SerializedName("paymentTypes") + */ + public $paymentTypes; + + /** + * @var \RetailCrm\Api\Model\Entity\Integration\Delivery\ShipmentPoint[] + * + * @JMS\Type("array") + * @JMS\SerializedName("shipmentPoints") + */ + public $shipmentPoints; + + /** + * @var \RetailCrm\Api\Model\Entity\Integration\Delivery\Status[] + * + * @JMS\Type("array") + * @JMS\SerializedName("statuses") + */ + public $statuses; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("deliveryExtraData") + */ + public $deliveryExtraData; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("shipmentExtraData") + */ + public $shipmentExtraData; +} diff --git a/src/Model/Entity/Integration/Delivery/ShipmentPoint.php b/src/Model/Entity/Integration/Delivery/ShipmentPoint.php new file mode 100644 index 0000000..0ce9a6e --- /dev/null +++ b/src/Model/Entity/Integration/Delivery/ShipmentPoint.php @@ -0,0 +1,45 @@ + + * + * @JMS\Type("array") + * @JMS\SerializedName("actions") + */ + public $actions; + + /** + * @var string[] + * + * @JMS\Type("array") + * @JMS\SerializedName("availableCountries") + */ + public $availableCountries; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("accountUrl") + */ + public $accountUrl; + + /** + * @var \RetailCrm\Api\Model\Entity\Integration\Integrations + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Integration\Integrations") + * @JMS\SerializedName("integrations") + */ + public $integrations; +} diff --git a/src/Model/Entity/Integration/IntegrationModuleEditInfo.php b/src/Model/Entity/Integration/IntegrationModuleEditInfo.php new file mode 100644 index 0000000..c589fee --- /dev/null +++ b/src/Model/Entity/Integration/IntegrationModuleEditInfo.php @@ -0,0 +1,53 @@ +") + * @JMS\SerializedName("notExistUsersIds") + */ + public $notExistUsersIds; + + /** + * @var string[] + * + * @JMS\Type("array") + * @JMS\SerializedName("notExistSiteCodes") + */ + public $notExistSiteCodes; +} diff --git a/src/Model/Entity/Integration/Integrations.php b/src/Model/Entity/Integration/Integrations.php new file mode 100644 index 0000000..2c0e3ad --- /dev/null +++ b/src/Model/Entity/Integration/Integrations.php @@ -0,0 +1,77 @@ +") + * @JMS\SerializedName("currencies") + */ + public $currencies; + + /** + * @var string[] + * + * @JMS\Type("array") + * @JMS\SerializedName("invoiceTypes") + */ + public $invoiceTypes; + + /** + * @var \RetailCrm\Api\Model\Entity\Integration\Payment\Shop[] + * + * @JMS\Type("array") + * @JMS\SerializedName("shops") + */ + public $shops; +} diff --git a/src/Model/Entity/Integration/Payment/Shop.php b/src/Model/Entity/Integration/Payment/Shop.php new file mode 100644 index 0000000..67a1b82 --- /dev/null +++ b/src/Model/Entity/Integration/Payment/Shop.php @@ -0,0 +1,67 @@ +code = $code; + } + + if ('' !== $name) { + $this->name = $name; + } + + if (null !== $active) { + $this->active = $active; + } + } +} diff --git a/src/Model/Entity/Integration/Recommendation/Mode.php b/src/Model/Entity/Integration/Recommendation/Mode.php new file mode 100644 index 0000000..d6a584d --- /dev/null +++ b/src/Model/Entity/Integration/Recommendation/Mode.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("names") + */ + public $names; +} diff --git a/src/Model/Entity/Integration/Recommendation/RecommendationConfiguration.php b/src/Model/Entity/Integration/Recommendation/RecommendationConfiguration.php new file mode 100644 index 0000000..4abb348 --- /dev/null +++ b/src/Model/Entity/Integration/Recommendation/RecommendationConfiguration.php @@ -0,0 +1,45 @@ +") + * @JMS\SerializedName("actions") + */ + public $actions; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("addDefaultModes") + */ + public $addDefaultModes; + + /** + * @var \RetailCrm\Api\Model\Entity\Integration\Recommendation\Mode[] + * + * @JMS\Type("array") + * @JMS\SerializedName("modes") + */ + public $modes; +} diff --git a/src/Model/Entity/Integration/Store/Action.php b/src/Model/Entity/Integration/Store/Action.php new file mode 100644 index 0000000..69c4627 --- /dev/null +++ b/src/Model/Entity/Integration/Store/Action.php @@ -0,0 +1,45 @@ +") + * @JMS\SerializedName("callPoints") + */ + public $callPoints; +} diff --git a/src/Model/Entity/Integration/Store/StoreConfiguration.php b/src/Model/Entity/Integration/Store/StoreConfiguration.php new file mode 100644 index 0000000..64a86d7 --- /dev/null +++ b/src/Model/Entity/Integration/Store/StoreConfiguration.php @@ -0,0 +1,29 @@ +") + * @JMS\SerializedName("actions") + */ + public $actions; +} diff --git a/src/Model/Entity/Integration/Telephony/SerializedAdditionalCodes.php b/src/Model/Entity/Integration/Telephony/SerializedAdditionalCodes.php new file mode 100644 index 0000000..75cf5fa --- /dev/null +++ b/src/Model/Entity/Integration/Telephony/SerializedAdditionalCodes.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("additionalCodes") + */ + public $additionalCodes; + + /** + * @var \RetailCrm\Api\Model\Entity\Integration\Telephony\SerializedExternalPhones[] + * + * @JMS\Type("array") + * @JMS\SerializedName("externalPhones") + */ + public $externalPhones; +} diff --git a/src/Model/Entity/Integration/Transport/TransportConfiguration.php b/src/Model/Entity/Integration/Transport/TransportConfiguration.php new file mode 100644 index 0000000..48e49ba --- /dev/null +++ b/src/Model/Entity/Integration/Transport/TransportConfiguration.php @@ -0,0 +1,53 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("activatedAt") + */ + public $activatedAt; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("deactivatedAt") + */ + public $deactivatedAt; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("blockedAt") + */ + public $blockedAt; +} diff --git a/src/Model/Entity/Loyalty/LoyaltyAccount.php b/src/Model/Entity/Loyalty/LoyaltyAccount.php new file mode 100644 index 0000000..c70c429 --- /dev/null +++ b/src/Model/Entity/Loyalty/LoyaltyAccount.php @@ -0,0 +1,134 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("activatedAt") + */ + public $activatedAt; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("confirmedPhoneAt") + */ + public $confirmedPhoneAt; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("lastCheckId") + */ + public $lastCheckId; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("customFields") + */ + public $customFields; + + /** + * @var \RetailCrm\Api\Model\Entity\CustomersCorporate\SerializedEntityCustomer + * + * @JMS\Type("RetailCrm\Api\Model\Entity\CustomersCorporate\SerializedEntityCustomer") + * @JMS\SerializedName("customer") + */ + public $customer; + + /** + * @var \RetailCrm\Api\Model\Entity\Loyalty\LoyaltyLevel + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Loyalty\LoyaltyLevel") + * @JMS\SerializedName("level") + */ + public $level; +} diff --git a/src/Model/Entity/Loyalty/LoyaltyBonus.php b/src/Model/Entity/Loyalty/LoyaltyBonus.php new file mode 100644 index 0000000..75ce566 --- /dev/null +++ b/src/Model/Entity/Loyalty/LoyaltyBonus.php @@ -0,0 +1,46 @@ +") + * @JMS\SerializedName("activationDate") + */ + public $activationDate; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d'>") + * @JMS\SerializedName("expireDate") + */ + public $expireDate; +} diff --git a/src/Model/Entity/Loyalty/LoyaltyCalculation.php b/src/Model/Entity/Loyalty/LoyaltyCalculation.php new file mode 100644 index 0000000..6d20263 --- /dev/null +++ b/src/Model/Entity/Loyalty/LoyaltyCalculation.php @@ -0,0 +1,69 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("amount") + */ + public $amount; + + /** + * @var \RetailCrm\Api\Model\Entity\Loyalty\OperationOrder + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Loyalty\OperationOrder") + * @JMS\SerializedName("order") + */ + public $order; + + /** + * @var \RetailCrm\Api\Model\Entity\Loyalty\OperationBonus + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Loyalty\OperationBonus") + * @JMS\SerializedName("bonus") + */ + public $bonus; + + /** + * @var \RetailCrm\Api\Model\Entity\Loyalty\OperationEvent + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Loyalty\OperationEvent") + * @JMS\SerializedName("event") + */ + public $event; +} diff --git a/src/Model/Entity/Loyalty/OperationBonus.php b/src/Model/Entity/Loyalty/OperationBonus.php new file mode 100644 index 0000000..158bed5 --- /dev/null +++ b/src/Model/Entity/Loyalty/OperationBonus.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("activationDate") + */ + public $activationDate; +} diff --git a/src/Model/Entity/Loyalty/OperationEvent.php b/src/Model/Entity/Loyalty/OperationEvent.php new file mode 100644 index 0000000..a4c7843 --- /dev/null +++ b/src/Model/Entity/Loyalty/OperationEvent.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("externalIds") + */ + public $externalIds; + + /** + * @var \RetailCrm\Api\Model\Entity\Loyalty\PriceType + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Loyalty\PriceType") + * @JMS\SerializedName("priceType") + */ + public $priceType; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("initialPrice") + */ + public $initialPrice; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("discountTotal") + */ + public $discountTotal; + + /** + * @var \RetailCrm\Api\Model\Entity\Loyalty\OrderProductPriceItem[] + * + * @JMS\Type("array") + * @JMS\SerializedName("prices") + */ + public $prices; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("vatRate") + */ + public $vatRate; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("quantity") + */ + public $quantity; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Items\Offer + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\Items\Offer") + * @JMS\SerializedName("offer") + */ + public $offer; +} diff --git a/src/Model/Entity/Loyalty/OrderProductPriceItem.php b/src/Model/Entity/Loyalty/OrderProductPriceItem.php new file mode 100644 index 0000000..3fcf3e2 --- /dev/null +++ b/src/Model/Entity/Loyalty/OrderProductPriceItem.php @@ -0,0 +1,37 @@ +code = $code; + } + } +} diff --git a/src/Model/Entity/Loyalty/SerializedCreateLoyaltyAccount.php b/src/Model/Entity/Loyalty/SerializedCreateLoyaltyAccount.php new file mode 100644 index 0000000..318b2f3 --- /dev/null +++ b/src/Model/Entity/Loyalty/SerializedCreateLoyaltyAccount.php @@ -0,0 +1,53 @@ + + * + * @JMS\Type("array") + * @JMS\SerializedName("customFields") + */ + public $customFields; + + /** + * @var \RetailCrm\Api\Model\Entity\CustomersCorporate\SerializedEntityCustomer + * + * @JMS\Type("RetailCrm\Api\Model\Entity\CustomersCorporate\SerializedEntityCustomer") + * @JMS\SerializedName("customer") + */ + public $customer; +} diff --git a/src/Model/Entity/Loyalty/SerializedLoyalty.php b/src/Model/Entity/Loyalty/SerializedLoyalty.php new file mode 100644 index 0000000..72e2ccf --- /dev/null +++ b/src/Model/Entity/Loyalty/SerializedLoyalty.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("items") + */ + public $items; +} diff --git a/src/Model/Entity/Loyalty/SerializedOrder.php b/src/Model/Entity/Loyalty/SerializedOrder.php new file mode 100644 index 0000000..d7ba517 --- /dev/null +++ b/src/Model/Entity/Loyalty/SerializedOrder.php @@ -0,0 +1,71 @@ +") + * @JMS\SerializedName("items") + */ + public $items; + + /** + * @var \RetailCrm\Api\Model\Entity\Loyalty\SerializedOrderDelivery + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Loyalty\SerializedOrderDelivery") + * @JMS\SerializedName("delivery") + */ + public $delivery; +} diff --git a/src/Model/Entity/Loyalty/SerializedOrderDelivery.php b/src/Model/Entity/Loyalty/SerializedOrderDelivery.php new file mode 100644 index 0000000..a40951f --- /dev/null +++ b/src/Model/Entity/Loyalty/SerializedOrderDelivery.php @@ -0,0 +1,41 @@ +cost = $cost; + } + } +} diff --git a/src/Model/Entity/Loyalty/SerializedOrderProduct.php b/src/Model/Entity/Loyalty/SerializedOrderProduct.php new file mode 100644 index 0000000..4b788b8 --- /dev/null +++ b/src/Model/Entity/Loyalty/SerializedOrderProduct.php @@ -0,0 +1,71 @@ +id = $id; + } + + if ('' !== $externalId) { + $this->externalId = $externalId; + } + + if ('' !== $xmlId) { + $this->xmlId = $xmlId; + } + } + + /** + * Returns product offer with specified ID. + * + * @param int $id + * + * @return self + */ + public static function withId(int $id): self + { + return new self($id); + } + + /** + * Returns product offer with specified external ID. + * + * @param string $externalId + * + * @return self + */ + public static function withExternalId(string $externalId): self + { + return new self(0, $externalId); + } + + /** + * Returns product offer with specified XML ID. + * + * @param string $xmlId + * + * @return self + */ + public static function withXmlId(string $xmlId): self + { + return new self(0, '', $xmlId); + } +} diff --git a/src/Model/Entity/Loyalty/SmsVerification.php b/src/Model/Entity/Loyalty/SmsVerification.php new file mode 100644 index 0000000..ee0ab01 --- /dev/null +++ b/src/Model/Entity/Loyalty/SmsVerification.php @@ -0,0 +1,62 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("expiredAt") + */ + public $expiredAt; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("verifiedAt") + */ + public $verifiedAt; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("checkId") + */ + public $checkId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("actionType") + */ + public $actionType; +} diff --git a/src/Model/Entity/Orders/Delivery/CourierPhone.php b/src/Model/Entity/Orders/Delivery/CourierPhone.php new file mode 100644 index 0000000..7fd6f58 --- /dev/null +++ b/src/Model/Entity/Orders/Delivery/CourierPhone.php @@ -0,0 +1,22 @@ +") + * @JMS\SerializedName("statusDate") + */ + public $statusDate; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("tariff") + */ + public $tariff; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("tariffName") + */ + public $tariffName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("pickuppointId") + */ + public $pickuppointId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("pickuppointSchedule") + */ + public $pickuppointSchedule; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("pickuppointPhone") + */ + public $pickuppointPhone; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("payerType") + */ + public $payerType; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("statusComment") + */ + public $statusComment; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("cost") + */ + public $cost; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("minTerm") + */ + public $minTerm; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("maxTerm") + */ + public $maxTerm; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("shipmentpointId") + */ + public $shipmentpointId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("shipmentpointName") + */ + public $shipmentpointName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("shipmentpointAddress") + */ + public $shipmentpointAddress; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("shipmentpointSchedule") + */ + public $shipmentpointSchedule; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("shipmentpointPhone") + */ + public $shipmentpointPhone; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("shipmentpointCoordinateLatitude") + */ + public $shipmentpointCoordinateLatitude; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("shipmentpointCoordinateLongitude") + */ + public $shipmentpointCoordinateLongitude; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("pickuppointName") + */ + public $pickuppointName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("pickuppointCoordinateLatitude") + */ + public $pickuppointCoordinateLatitude; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("pickuppointCoordinateLongitude") + */ + public $pickuppointCoordinateLongitude; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("extraData") + */ + public $extraData; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Delivery\Package[] + * + * @JMS\Type("array") + * @JMS\SerializedName("packages") + */ + public $packages; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("returnExternalId") + */ + public $returnExternalId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("deliveryName") + */ + public $deliveryName; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("price") + */ + public $price; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("insurancePrice") + */ + public $insurancePrice; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("tariffType") + */ + public $tariffType; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("receiverCity") + */ + public $receiverCity; + + /** + * @var string[] + * + * @JMS\Type("array") + * @JMS\SerializedName("services") + */ + public $services; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("packageNumber") + */ + public $packageNumber; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("comment") + */ + public $comment; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("deliveryCode") + */ + public $deliveryCode; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("notes") + */ + public $notes; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("id") + */ + public $id; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("firstName") + */ + public $firstName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("lastName") + */ + public $lastName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("patronymic") + */ + public $patronymic; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("active") + */ + public $active; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("email") + */ + public $email; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Delivery\CourierPhone + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\Delivery\CourierPhone") + * @JMS\SerializedName("phone") + */ + public $phone; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("description") + */ + public $description; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("courierId") + */ + public $courierId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("serviceType") + */ + public $serviceType; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("pickuppoint") + */ + public $pickuppoint; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("receiverWarehouseTypeRef") + */ + public $receiverWarehouseTypeRef; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("statusName") + */ + public $statusName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("receiverCityRef") + */ + public $receiverCityRef; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("receiverStreet") + */ + public $receiverStreet; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("receiverStreetRef") + */ + public $receiverStreetRef; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("seatsAmount") + */ + public $seatsAmount; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("cargoType") + */ + public $cargoType; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("cargoDescription") + */ + public $cargoDescription; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("cashPayerType") + */ + public $cashPayerType; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("paymentForm") + */ + public $paymentForm; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("ownershipForm") + */ + public $ownershipForm; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("accompanyingDocument") + */ + public $accompanyingDocument; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("preferredDeliveryDate") + */ + public $preferredDeliveryDate; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("timeInterval") + */ + public $timeInterval; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("saturdayDelivery") + */ + public $saturdayDelivery; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("deliveryDate") + */ + public $deliveryDate; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("denieReason") + */ + public $denieReason; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("backwardDelivery") + */ + public $backwardDelivery; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("backwardDeliveryCargoType") + */ + public $backwardDeliveryCargoType; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("backwardDeliveryPayerType") + */ + public $backwardDeliveryPayerType; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("backwardDeliveryRedeliveryString") + */ + public $backwardDeliveryRedeliveryString; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("afterpaymentOnGoodsCost") + */ + public $afterpaymentOnGoodsCost; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("declaredValue") + */ + public $declaredValue; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("sendDate") + */ + public $sendDate; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("deliveryType") + */ + public $deliveryType; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("pickupType") + */ + public $pickupType; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("pickuppointDescription") + */ + public $pickuppointDescription; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("placesCount") + */ + public $placesCount; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("service") + */ + public $service; +} diff --git a/src/Model/Entity/Orders/Delivery/OrderDeliveryAddress.php b/src/Model/Entity/Orders/Delivery/OrderDeliveryAddress.php new file mode 100644 index 0000000..fffd8ee --- /dev/null +++ b/src/Model/Entity/Orders/Delivery/OrderDeliveryAddress.php @@ -0,0 +1,181 @@ +") + * @JMS\SerializedName("items") + */ + public $items; +} diff --git a/src/Model/Entity/Orders/Delivery/PackageItem.php b/src/Model/Entity/Orders/Delivery/PackageItem.php new file mode 100644 index 0000000..03ee0d3 --- /dev/null +++ b/src/Model/Entity/Orders/Delivery/PackageItem.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("externalIds") + */ + public $externalIds; +} diff --git a/src/Model/Entity/Orders/Delivery/SerializedDeliveryService.php b/src/Model/Entity/Orders/Delivery/SerializedDeliveryService.php new file mode 100644 index 0000000..9e2aaa8 --- /dev/null +++ b/src/Model/Entity/Orders/Delivery/SerializedDeliveryService.php @@ -0,0 +1,45 @@ +") + * @JMS\SerializedName("date") + */ + public $date; + + /** + * @var \RetailCrm\Api\Model\Entity\Delivery\TimeInterval + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Delivery\TimeInterval") + * @JMS\SerializedName("time") + */ + public $time; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Delivery\OrderDeliveryAddress + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\Delivery\OrderDeliveryAddress") + * @JMS\SerializedName("address") + */ + public $address; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("vatRate") + */ + public $vatRate; +} diff --git a/src/Model/Entity/Orders/Items/AbstractDiscount.php b/src/Model/Entity/Orders/Items/AbstractDiscount.php new file mode 100644 index 0000000..1fde767 --- /dev/null +++ b/src/Model/Entity/Orders/Items/AbstractDiscount.php @@ -0,0 +1,49 @@ +type = $type; + $this->amount = $amount; + } +} diff --git a/src/Model/Entity/Orders/Items/ItemProperty.php b/src/Model/Entity/Orders/Items/ItemProperty.php new file mode 100644 index 0000000..bb45ca0 --- /dev/null +++ b/src/Model/Entity/Orders/Items/ItemProperty.php @@ -0,0 +1,45 @@ + + * + * @JMS\Type("array") + * @JMS\SerializedName("properties") + */ + public $properties; +} diff --git a/src/Model/Entity/Orders/Items/OrderProduct.php b/src/Model/Entity/Orders/Items/OrderProduct.php new file mode 100644 index 0000000..6ca6c8c --- /dev/null +++ b/src/Model/Entity/Orders/Items/OrderProduct.php @@ -0,0 +1,216 @@ +") + * @JMS\SerializedName("externalIds") + */ + public $externalIds; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Items\PriceType + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\Items\PriceType") + * @JMS\SerializedName("priceType") + */ + public $priceType; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("initialPrice") + */ + public $initialPrice; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("discountManualAmount") + */ + public $discountManualAmount; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("discountManualPercent") + */ + public $discountManualPercent; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("discountTotal") + */ + public $discountTotal; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Items\OrderProductPriceItem[] + * + * @JMS\Type("array") + * @JMS\SerializedName("prices") + */ + public $prices; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("vatRate") + */ + public $vatRate; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("quantity") + */ + public $quantity; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("status") + */ + public $status; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("comment") + */ + public $comment; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Items\Offer + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\Items\Offer") + * @JMS\SerializedName("offer") + */ + public $offer; + + /** + * @var \RetailCrm\Api\Model\Entity\IdModel + * + * @JMS\Type("RetailCrm\Api\Model\Entity\IdModel") + * @JMS\SerializedName("order") + */ + public $order; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("productName") + */ + public $productName; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("isCanceled") + */ + public $isCanceled; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("properties") + */ + public $properties; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Items\AbstractDiscount[] + * + * @JMS\Type("array") + * @JMS\SerializedName("discounts") + */ + public $discounts; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("purchasePrice") + */ + public $purchasePrice; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("bonusesCreditTotal") + */ + public $bonusesCreditTotal; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("bonusesChargeTotal") + */ + public $bonusesChargeTotal; +} diff --git a/src/Model/Entity/Orders/Items/OrderProductPriceItem.php b/src/Model/Entity/Orders/Items/OrderProductPriceItem.php new file mode 100644 index 0000000..5f09006 --- /dev/null +++ b/src/Model/Entity/Orders/Items/OrderProductPriceItem.php @@ -0,0 +1,37 @@ +code = $code; + } + } +} diff --git a/src/Model/Entity/Orders/Items/Unit.php b/src/Model/Entity/Orders/Items/Unit.php new file mode 100644 index 0000000..a734ac8 --- /dev/null +++ b/src/Model/Entity/Orders/Items/Unit.php @@ -0,0 +1,67 @@ +code = $code; + } + + if ('' !== $name) { + $this->name = $name; + } + + if ('' !== $sym) { + $this->sym = $sym; + } + } +} diff --git a/src/Model/Entity/Orders/MarketplaceData.php b/src/Model/Entity/Orders/MarketplaceData.php new file mode 100644 index 0000000..cbf801f --- /dev/null +++ b/src/Model/Entity/Orders/MarketplaceData.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("statusUpdatedAt") + */ + public $statusUpdatedAt; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("discountManualAmount") + */ + public $discountManualAmount; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("discountManualPercent") + */ + public $discountManualPercent; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("totalSumm") + */ + public $totalSumm; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("prepaySum") + */ + public $prepaySum; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("purchaseSumm") + */ + public $purchaseSumm; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("personalDiscountPercent") + */ + public $personalDiscountPercent; + + /** + * @var \RetailCrm\Api\Model\Entity\Loyalty\LoyaltyLevel + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Loyalty\LoyaltyLevel") + * @JMS\SerializedName("loyaltyLevel") + */ + public $loyaltyLevel; + + /** + * @var \RetailCrm\Api\Model\Entity\Loyalty\LoyaltyEventDiscount + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Loyalty\LoyaltyEventDiscount") + * @JMS\SerializedName("loyaltyEventDiscount") + */ + public $loyaltyEventDiscount; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("mark") + */ + public $mark; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("markDatetime") + */ + public $markDatetime; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("lastName") + */ + public $lastName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("firstName") + */ + public $firstName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("patronymic") + */ + public $patronymic; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("phone") + */ + public $phone; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("additionalPhone") + */ + public $additionalPhone; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("email") + */ + public $email; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("call") + */ + public $call; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("expired") + */ + public $expired; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("customerComment") + */ + public $customerComment; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("managerComment") + */ + public $managerComment; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("managerId") + */ + public $managerId; + + /** + * @var \RetailCrm\Api\Interfaces\Orders\CustomerInterface + * + * @JMS\Type("RetailCrm\Api\Interfaces\Orders\CustomerInterface") + * @JMS\SerializedName("customer") + */ + public $customer; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\Customer + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Customers\Customer") + * @JMS\SerializedName("contact") + */ + public $contact; + + /** + * @var \RetailCrm\Api\Model\Entity\CustomersCorporate\Company + * + * @JMS\Type("RetailCrm\Api\Model\Entity\CustomersCorporate\Company") + * @JMS\SerializedName("company") + */ + public $company; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\OrderContragent + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\OrderContragent") + * @JMS\SerializedName("contragent") + */ + public $contragent; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Delivery\SerializedOrderDelivery + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\Delivery\SerializedOrderDelivery") + * @JMS\SerializedName("delivery") + */ + public $delivery; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\MarketplaceData + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\MarketplaceData") + * @JMS\SerializedName("marketplace") + */ + public $marketplace; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("site") + */ + public $site; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("status") + */ + public $status; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("statusComment") + */ + public $statusComment; + + /** + * @var \RetailCrm\Api\Model\Entity\Source + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Source") + * @JMS\SerializedName("source") + */ + public $source; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Items\OrderProduct[] + * + * @JMS\Type("array") + * @JMS\SerializedName("items") + */ + public $items; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("fullPaidAt") + */ + public $fullPaidAt; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Payment[] + * + * @JMS\Type("array") + * @JMS\SerializedName("payments") + */ + public $payments; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("fromApi") + */ + public $fromApi; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("weight") + */ + public $weight; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("length") + */ + public $length; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("width") + */ + public $width; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("height") + */ + public $height; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("shipmentStore") + */ + public $shipmentStore; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d'>") + * @JMS\SerializedName("shipmentDate") + */ + public $shipmentDate; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("shipped") + */ + public $shipped; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("dialogId") + */ + public $dialogId; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("customFields") + */ + public $customFields; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("clientId") + */ + public $clientId; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("loyaltyEventId") + */ + public $loyaltyEventId; +} diff --git a/src/Model/Entity/Orders/OrderContragent.php b/src/Model/Entity/Orders/OrderContragent.php new file mode 100644 index 0000000..74b2720 --- /dev/null +++ b/src/Model/Entity/Orders/OrderContragent.php @@ -0,0 +1,22 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("created") + */ + public $created; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("deleted") + */ + public $deleted; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("source") + */ + public $source; + + /** + * @var \RetailCrm\Api\Model\Entity\HistoryUser + * + * @JMS\Type("RetailCrm\Api\Model\Entity\HistoryUser") + * @JMS\SerializedName("user") + */ + public $user; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("field") + */ + public $field; + + /** + * @var mixed + * + * @JMS\Type("mixed") + * @JMS\SerializedName("oldValue") + */ + public $oldValue; + + /** + * @var mixed + * + * @JMS\Type("mixed") + * @JMS\SerializedName("newValue") + */ + public $newValue; + + /** + * @var \RetailCrm\Api\Model\Entity\HistoryApiKey + * + * @JMS\Type("RetailCrm\Api\Model\Entity\HistoryApiKey") + * @JMS\SerializedName("apiKey") + */ + public $apiKey; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Order + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\Order") + * @JMS\SerializedName("order") + */ + public $order; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Order + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\Order") + * @JMS\SerializedName("ancestor") + */ + public $ancestor; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Items\OrderProduct + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\Items\OrderProduct") + * @JMS\SerializedName("item") + */ + public $item; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Payment + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\Payment") + * @JMS\SerializedName("payment") + */ + public $payment; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Order + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\Order") + * @JMS\SerializedName("combinedTo") + */ + public $combinedTo; +} diff --git a/src/Model/Entity/Orders/OrderStatusItem.php b/src/Model/Entity/Orders/OrderStatusItem.php new file mode 100644 index 0000000..3c3345b --- /dev/null +++ b/src/Model/Entity/Orders/OrderStatusItem.php @@ -0,0 +1,53 @@ +") + * @JMS\SerializedName("paidAt") + */ + public $paidAt; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("comment") + */ + public $comment; +} diff --git a/src/Model/Entity/Orders/SerializedOrderLink.php b/src/Model/Entity/Orders/SerializedOrderLink.php new file mode 100644 index 0000000..072730d --- /dev/null +++ b/src/Model/Entity/Orders/SerializedOrderLink.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("orders") + */ + public $orders; +} diff --git a/src/Model/Entity/Orders/SerializedOrderReference.php b/src/Model/Entity/Orders/SerializedOrderReference.php new file mode 100644 index 0000000..dbd19d8 --- /dev/null +++ b/src/Model/Entity/Orders/SerializedOrderReference.php @@ -0,0 +1,41 @@ +id = $id; + } + } +} diff --git a/src/Model/Entity/Orders/SerializedPayment.php b/src/Model/Entity/Orders/SerializedPayment.php new file mode 100644 index 0000000..729ec67 --- /dev/null +++ b/src/Model/Entity/Orders/SerializedPayment.php @@ -0,0 +1,29 @@ +id = $id; + $customer->type = $type; + + return $customer; + } + + /** + * Returns customer with provided external ID, site and type. + * + * @param string $externalId + * @param string $type + * @param string $site + * + * @return self + */ + public static function withExternalIdAndType(string $externalId, string $type, string $site): self + { + $customer = new self(); + $customer->externalId = $externalId; + $customer->type = $type; + $customer->site = $site; + + return $customer; + } +} diff --git a/src/Model/Entity/Packs/OrderProductPack.php b/src/Model/Entity/Packs/OrderProductPack.php new file mode 100644 index 0000000..2425fca --- /dev/null +++ b/src/Model/Entity/Packs/OrderProductPack.php @@ -0,0 +1,102 @@ +") + * @JMS\SerializedName("shipmentDate") + */ + public $shipmentDate; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("invoiceNumber") + */ + public $invoiceNumber; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("deliveryNoteNumber") + */ + public $deliveryNoteNumber; +} diff --git a/src/Model/Entity/Packs/OrderProductPackHistory.php b/src/Model/Entity/Packs/OrderProductPackHistory.php new file mode 100644 index 0000000..1cb7ec0 --- /dev/null +++ b/src/Model/Entity/Packs/OrderProductPackHistory.php @@ -0,0 +1,102 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("created") + */ + public $created; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("deleted") + */ + public $deleted; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("field") + */ + public $field; + + /** + * @var mixed + * + * @JMS\Type("mixed") + * @JMS\SerializedName("oldValue") + */ + public $oldValue; + + /** + * @var mixed + * + * @JMS\Type("mixed") + * @JMS\SerializedName("newValue") + */ + public $newValue; + + /** + * @var \RetailCrm\Api\Model\Entity\Packs\OrderProductPack + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Packs\OrderProductPack") + * @JMS\SerializedName("pack") + */ + public $pack; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("source") + */ + public $source; + + /** + * @var \RetailCrm\Api\Model\Entity\HistoryUser + * + * @JMS\Type("RetailCrm\Api\Model\Entity\HistoryUser") + * @JMS\SerializedName("user") + */ + public $user; +} diff --git a/src/Model/Entity/Pagination.php b/src/Model/Entity/Pagination.php new file mode 100644 index 0000000..362df16 --- /dev/null +++ b/src/Model/Entity/Pagination.php @@ -0,0 +1,53 @@ + + * + * @JMS\Type("array") + * @JMS\SerializedName("paymentTypes") + */ + public $paymentTypes; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("integrationCode") + */ + public $integrationCode; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("deliveryServices") + */ + public $deliveryServices; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("defaultForCrm") + */ + public $defaultForCrm; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("vatRate") + */ + public $vatRate; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("defaultTariffCode") + */ + public $defaultTariffCode; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("defaultTariffType") + */ + public $defaultTariffType; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("defaultTariffName") + */ + public $defaultTariffName; +} diff --git a/src/Model/Entity/References/GeoHierarchyRow.php b/src/Model/Entity/References/GeoHierarchyRow.php new file mode 100644 index 0000000..47adaf3 --- /dev/null +++ b/src/Model/Entity/References/GeoHierarchyRow.php @@ -0,0 +1,61 @@ +") + * @JMS\SerializedName("certificateDate") + */ + public $certificateDate; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("BIK") + */ + public $BIK; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("bank") + */ + public $bank; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("bankAddress") + */ + public $bankAddress; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("corrAccount") + */ + public $corrAccount; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("bankAccount") + */ + public $bankAccount; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("code") + */ + public $code; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("countryIso") + */ + public $countryIso; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("vatRate") + */ + public $vatRate; + + /** + * LegalEntity constructor. + * + * @param string $code + */ + public function __construct(string $code = '') + { + if ('' !== $code) { + $this->code = $code; + } + } +} diff --git a/src/Model/Entity/References/MGChannel.php b/src/Model/Entity/References/MGChannel.php new file mode 100644 index 0000000..c2f87d0 --- /dev/null +++ b/src/Model/Entity/References/MGChannel.php @@ -0,0 +1,61 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("orderStatusByProductStatus") + */ + public $orderStatusByProductStatus; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("orderStatusForProductStatus") + */ + public $orderStatusForProductStatus; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("cancelStatus") + */ + public $cancelStatus; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("name") + */ + public $name; +} diff --git a/src/Model/Entity/References/OrderType.php b/src/Model/Entity/References/OrderType.php new file mode 100644 index 0000000..5f87efe --- /dev/null +++ b/src/Model/Entity/References/OrderType.php @@ -0,0 +1,69 @@ +") + * @JMS\SerializedName("geo") + */ + public $geo; + + /** + * @var string[] + * + * @JMS\Type("array") + * @JMS\SerializedName("groups") + */ + public $groups; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("ordering") + */ + public $ordering; +} diff --git a/src/Model/Entity/References/SerializedUnit.php b/src/Model/Entity/References/SerializedUnit.php new file mode 100644 index 0000000..c309aaf --- /dev/null +++ b/src/Model/Entity/References/SerializedUnit.php @@ -0,0 +1,61 @@ +") + * @JMS\SerializedName("catalogUpdatedAt") + */ + public $catalogUpdatedAt; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("catalogLoadingAt") + */ + public $catalogLoadingAt; + + /** + * @var \RetailCrm\Api\Model\Entity\References\LegalEntity + * + * @JMS\Type("RetailCrm\Api\Model\Entity\References\LegalEntity") + * @JMS\SerializedName("contragent") + */ + public $contragent; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("contragentCode") + */ + public $contragentCode; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("countryIso") + */ + public $countryIso; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("senderEmail") + */ + public $senderEmail; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("senderName") + */ + public $senderName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("catalogId") + */ + public $catalogId; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("isCatalogMainSite") + */ + public $isCatalogMainSite; +} diff --git a/src/Model/Entity/References/Status.php b/src/Model/Entity/References/Status.php new file mode 100644 index 0000000..ddf2809 --- /dev/null +++ b/src/Model/Entity/References/Status.php @@ -0,0 +1,61 @@ +") + * @JMS\SerializedName("updated_at") + */ + public $updatedAt; +} diff --git a/src/Model/Entity/Source.php b/src/Model/Entity/Source.php new file mode 100644 index 0000000..f11d136 --- /dev/null +++ b/src/Model/Entity/Source.php @@ -0,0 +1,61 @@ +code = $code; + } + + if (null !== $available) { + $this->available = $available; + } + + if (null !== $purchasePrice) { + $this->purchasePrice = $purchasePrice; + } + } +} diff --git a/src/Model/Entity/Store/Offer.php b/src/Model/Entity/Store/Offer.php new file mode 100644 index 0000000..f22a286 --- /dev/null +++ b/src/Model/Entity/Store/Offer.php @@ -0,0 +1,54 @@ +") + * @JMS\SerializedName("stores") + */ + public $stores; +} diff --git a/src/Model/Entity/Store/OfferPrice.php b/src/Model/Entity/Store/OfferPrice.php new file mode 100644 index 0000000..c03f829 --- /dev/null +++ b/src/Model/Entity/Store/OfferPrice.php @@ -0,0 +1,45 @@ +") + * @JMS\SerializedName("prices") + */ + public $prices; +} diff --git a/src/Model/Entity/Store/PriceUploadNotFoundResponse.php b/src/Model/Entity/Store/PriceUploadNotFoundResponse.php new file mode 100644 index 0000000..1e17eaf --- /dev/null +++ b/src/Model/Entity/Store/PriceUploadNotFoundResponse.php @@ -0,0 +1,45 @@ +code = $code; + } + + if (null !== $price) { + $this->price = $price; + } + + if (null !== $remove) { + $this->remove = $remove; + } + } +} diff --git a/src/Model/Entity/Store/Product.php b/src/Model/Entity/Store/Product.php new file mode 100644 index 0000000..81c21ce --- /dev/null +++ b/src/Model/Entity/Store/Product.php @@ -0,0 +1,190 @@ + + * + * @JMS\Type("array") + * @JMS\SerializedName("properties") + */ + public $properties; + + /** + * @var \RetailCrm\Api\Model\Entity\FixExternalRow[] + * + * @JMS\Type("array") + * @JMS\SerializedName("groups") + */ + public $groups; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("externalId") + */ + public $externalId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("manufacturer") + */ + public $manufacturer; + + /** + * @var \RetailCrm\Api\Model\Entity\Store\ProductOffer[] + * + * @JMS\Type("array") + * @JMS\SerializedName("offers") + */ + public $offers; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("active") + */ + public $active; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("quantity") + */ + public $quantity; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("markable") + */ + public $markable; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("updatedAt") + */ + public $updatedAt; +} diff --git a/src/Model/Entity/Store/ProductGroup.php b/src/Model/Entity/Store/ProductGroup.php new file mode 100644 index 0000000..c150374 --- /dev/null +++ b/src/Model/Entity/Store/ProductGroup.php @@ -0,0 +1,69 @@ +") + * @JMS\SerializedName("images") + */ + public $images; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("id") + */ + public $id; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("externalId") + */ + public $externalId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("xmlId") + */ + public $xmlId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("article") + */ + public $article; + + /** + * @var \RetailCrm\Api\Model\Entity\Store\OfferPrice[] + * + * @JMS\Type("array") + * @JMS\SerializedName("prices") + */ + public $prices; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("purchasePrice") + */ + public $purchasePrice; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("vatRate") + */ + public $vatRate; + + /** + * @var array + * + * @JMS\Type("array") + * @JMS\SerializedName("properties") + */ + public $properties; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("quantity") + */ + public $quantity; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("weight") + */ + public $weight; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("length") + */ + public $length; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("width") + */ + public $width; + + /** + * @var float + * + * @JMS\Type("float") + * @JMS\SerializedName("height") + */ + public $height; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("active") + */ + public $active; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Items\Unit + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Orders\Items\Unit") + * @JMS\SerializedName("unit") + */ + public $unit; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("barcode") + */ + public $barcode; +} diff --git a/src/Model/Entity/Tasks/AbstractCustomer.php b/src/Model/Entity/Tasks/AbstractCustomer.php new file mode 100644 index 0000000..3889f17 --- /dev/null +++ b/src/Model/Entity/Tasks/AbstractCustomer.php @@ -0,0 +1,80 @@ +id = $id; + } + + if ('' !== $externalId) { + $this->externalId = $externalId; + } + + if ('' !== $type) { + $this->type = $type; + } + + if ('' !== $site) { + $this->site = $site; + } + } +} diff --git a/src/Model/Entity/Tasks/Order.php b/src/Model/Entity/Tasks/Order.php new file mode 100644 index 0000000..fdf17df --- /dev/null +++ b/src/Model/Entity/Tasks/Order.php @@ -0,0 +1,53 @@ +") + * @JMS\SerializedName("datetime") + */ + public $datetime; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("complete") + */ + public $complete; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("creator") + */ + public $creator; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("performer") + */ + public $performer; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("performerId") + */ + public $performerId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("performerType") + */ + public $performerType; + + /** + * @var \RetailCrm\Api\Model\Entity\Tasks\AbstractCustomer + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Tasks\AbstractCustomer") + * @JMS\SerializedName("customer") + */ + public $customer; + + /** + * @var \RetailCrm\Api\Model\Entity\Tasks\Order + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Tasks\Order") + * @JMS\SerializedName("order") + */ + public $order; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("phone") + */ + public $phone; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("phoneSite") + */ + public $phoneSite; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("completedAt") + */ + public $completedAt; +} diff --git a/src/Model/Entity/Telephony/Call.php b/src/Model/Entity/Telephony/Call.php new file mode 100644 index 0000000..8c9f454 --- /dev/null +++ b/src/Model/Entity/Telephony/Call.php @@ -0,0 +1,134 @@ +") + * @JMS\SerializedName("date") + */ + public $date; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("type") + */ + public $type; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("phone") + */ + public $phone; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("code") + */ + public $code; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("userId") + */ + public $userId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("result") + */ + public $result; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("duration") + */ + public $duration; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("durationWaiting") + */ + public $durationWaiting; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("externalId") + */ + public $externalId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("recordUrl") + */ + public $recordUrl; + + /** + * @var \RetailCrm\Api\Model\Entity\Source + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Source") + * @JMS\SerializedName("source") + */ + public $source; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("externalPhone") + */ + public $externalPhone; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("site") + */ + public $site; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("clientId") + */ + public $clientId; +} diff --git a/src/Model/Entity/Telephony/CallEvent.php b/src/Model/Entity/Telephony/CallEvent.php new file mode 100644 index 0000000..fa5553b --- /dev/null +++ b/src/Model/Entity/Telephony/CallEvent.php @@ -0,0 +1,93 @@ +") + * @JMS\SerializedName("codes") + */ + public $codes; + + /** + * @var int[] + * + * @JMS\Type("array") + * @JMS\SerializedName("userIds") + */ + public $userIds; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("site") + */ + public $site; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("hangupStatus") + */ + public $hangupStatus; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("externalPhone") + */ + public $externalPhone; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("callExternalId") + */ + public $callExternalId; + + /** + * @var \RetailCrm\Api\Model\Entity\Telephony\SerializedWebAnalyticsData + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Telephony\SerializedWebAnalyticsData") + * @JMS\SerializedName("webAnalyticsData") + */ + public $webAnalyticsData; +} diff --git a/src/Model/Entity/Telephony/SerializedCampaign.php b/src/Model/Entity/Telephony/SerializedCampaign.php new file mode 100644 index 0000000..af1d542 --- /dev/null +++ b/src/Model/Entity/Telephony/SerializedCampaign.php @@ -0,0 +1,37 @@ +") + * @JMS\SerializedName("phones") + */ + public $phones; +} diff --git a/src/Model/Entity/Telephony/SerializedLinks.php b/src/Model/Entity/Telephony/SerializedLinks.php new file mode 100644 index 0000000..b3a9648 --- /dev/null +++ b/src/Model/Entity/Telephony/SerializedLinks.php @@ -0,0 +1,53 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("active") + */ + public $active; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("email") + */ + public $email; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("firstName") + */ + public $firstName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("lastName") + */ + public $lastName; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("patronymic") + */ + public $patronymic; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("photoUrl") + */ + public $photoUrl; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("phone") + */ + public $phone; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("status") + */ + public $status; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("online") + */ + public $online; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("isAdmin") + */ + public $isAdmin; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("isManager") + */ + public $isManager; + + /** + * @var \RetailCrm\Api\Model\Entity\Users\Group[] + * + * @JMS\Type("array") + * @JMS\SerializedName("groups") + */ + public $groups; + + /** + * @var int + * + * @JMS\Type("int") + * @JMS\SerializedName("mgUserId") + */ + public $mgUserId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("senderEmail") + */ + public $senderEmail; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("senderName") + */ + public $senderName; +} diff --git a/src/Model/Entity/Verification/SmsVerification.php b/src/Model/Entity/Verification/SmsVerification.php new file mode 100644 index 0000000..c41a45b --- /dev/null +++ b/src/Model/Entity/Verification/SmsVerification.php @@ -0,0 +1,62 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("expiredAt") + */ + public $expiredAt; + + /** + * @var DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("verifiedAt") + */ + public $verifiedAt; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("checkId") + */ + public $checkId; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("actionType") + */ + public $actionType; +} diff --git a/src/Model/Entity/Verification/SmsVerificationConfirm.php b/src/Model/Entity/Verification/SmsVerificationConfirm.php new file mode 100644 index 0000000..18aacac --- /dev/null +++ b/src/Model/Entity/Verification/SmsVerificationConfirm.php @@ -0,0 +1,37 @@ + + * + * @Form\Type("array") + * @Form\SerializedName("customFields") + */ + public $customFields; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("browserId") + */ + public $browserId; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("commentary") + */ + public $commentary; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("sourceName") + */ + public $sourceName; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("mediumName") + */ + public $mediumName; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("campaignName") + */ + public $campaignName; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("keywordName") + */ + public $keywordName; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("adContentName") + */ + public $adContentName; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("mgCustomerId") + */ + public $mgCustomerId; + + /** + * @var string[] + * + * @Form\Type("string[]") + * @Form\SerializedName("tags") + */ + public $tags; + + /** + * @var string[] + * + * @Form\Type("string[]") + * @Form\SerializedName("attachedTags") + */ + public $attachedTags; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("mgCustomerIds") + */ + public $mgCustomerIds; +} diff --git a/src/Model/Filter/Customers/CustomerHistoryFilter.php b/src/Model/Filter/Customers/CustomerHistoryFilter.php new file mode 100644 index 0000000..ca5fc01 --- /dev/null +++ b/src/Model/Filter/Customers/CustomerHistoryFilter.php @@ -0,0 +1,62 @@ +") + * @Form\SerializedName("startDate") + */ + public $startDate; + + /** + * @var DateTime|null + * + * @Form\Type("DateTime<'Y-m-d H:i:s'>") + * @Form\SerializedName("endDate") + */ + public $endDate; +} diff --git a/src/Model/Filter/Customers/CustomerNoteFilter.php b/src/Model/Filter/Customers/CustomerNoteFilter.php new file mode 100644 index 0000000..8fb4961 --- /dev/null +++ b/src/Model/Filter/Customers/CustomerNoteFilter.php @@ -0,0 +1,77 @@ + + * + * @Form\Type("array") + * @Form\SerializedName("customFields") + */ + public $customFields; +} diff --git a/src/Model/Filter/Loyalty/LoyaltyAccountBonusOperationsApiFilterType.php b/src/Model/Filter/Loyalty/LoyaltyAccountBonusOperationsApiFilterType.php new file mode 100644 index 0000000..03aa905 --- /dev/null +++ b/src/Model/Filter/Loyalty/LoyaltyAccountBonusOperationsApiFilterType.php @@ -0,0 +1,39 @@ +") + * @Form\SerializedName("createdAtFrom") + */ + public $createdAtFrom; + + /** + * @var \DateTime + * + * @Form\Type("DateTime<'Y-m-d H:i:s'>") + * @Form\SerializedName("createdAtTo") + */ + public $createdAtTo; +} diff --git a/src/Model/Filter/Loyalty/LoyaltyApiFilterType.php b/src/Model/Filter/Loyalty/LoyaltyApiFilterType.php new file mode 100644 index 0000000..d0e58da --- /dev/null +++ b/src/Model/Filter/Loyalty/LoyaltyApiFilterType.php @@ -0,0 +1,53 @@ +") + * @Form\SerializedName("deliveryTimeFrom") + */ + public $deliveryTimeFrom; + + /** + * @var DateTime + * + * @Form\Type("DateTime<'H:i:s'>") + * @Form\SerializedName("deliveryTimeTo") + */ + public $deliveryTimeTo; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("minPrepaySumm") + */ + public $minPrepaySumm; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("maxPrepaySumm") + */ + public $maxPrepaySumm; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("minPrice") + */ + public $minPrice; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("maxPrice") + */ + public $maxPrice; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("product") + */ + public $product; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("vip") + */ + public $vip; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("bad") + */ + public $bad; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("expired") + */ + public $expired; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("call") + */ + public $call; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("online") + */ + public $online; + + /** + * @var string[] + * + * @Form\Type("string[]") + * @Form\SerializedName("deliveryStates") + */ + public $deliveryStates; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("minDeliveryCost") + */ + public $minDeliveryCost; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("maxDeliveryCost") + */ + public $maxDeliveryCost; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("minDeliveryNetCost") + */ + public $minDeliveryNetCost; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("maxDeliveryNetCost") + */ + public $maxDeliveryNetCost; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("managerComment") + */ + public $managerComment; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("customerComment") + */ + public $customerComment; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("minMarginSumm") + */ + public $minMarginSumm; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("maxMarginSumm") + */ + public $maxMarginSumm; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("minPurchaseSumm") + */ + public $minPurchaseSumm; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("maxPurchaseSumm") + */ + public $maxPurchaseSumm; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("minCostSumm") + */ + public $minCostSumm; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("maxCostSumm") + */ + public $maxCostSumm; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("trackNumber") + */ + public $trackNumber; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("deliveryExternalId") + */ + public $deliveryExternalId; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("contragentName") + */ + public $contragentName; + + /** + * @var string[] + * + * @Form\Type("string[]") + * @Form\SerializedName("contragentTypes") + */ + public $contragentTypes; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("contragentInn") + */ + public $contragentInn; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("contragentKpp") + */ + public $contragentKpp; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("contragentBik") + */ + public $contragentBik; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("contragentCorrAccount") + */ + public $contragentCorrAccount; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("contragentBankAccount") + */ + public $contragentBankAccount; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("shipped") + */ + public $shipped; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("attachments") + */ + public $attachments; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("receiptFiscalDocumentAttribute") + */ + public $receiptFiscalDocumentAttribute; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("receiptStatus") + */ + public $receiptStatus; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("receiptOperation") + */ + public $receiptOperation; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("receiptOrderStatus") + */ + public $receiptOrderStatus; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("tasksCounts") + */ + public $tasksCounts; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("customerType") + */ + public $customerType; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("companyName") + */ + public $companyName; + + /** + * @var int[] + * + * @Form\Type("int[]") + * @Form\SerializedName("ids") + */ + public $ids; + + /** + * @var string[] + * + * @Form\Type("string[]") + * @Form\SerializedName("orderTypes") + */ + public $orderTypes; + + /** + * @var int[] + * + * @Form\Type("int[]") + * @Form\SerializedName("managers") + */ + public $managers; + + /** + * @var string[] + * + * @Form\Type("string[]") + * @Form\SerializedName("paymentStatuses") + */ + public $paymentStatuses; + + /** + * @var string[] + * + * @Form\Type("string[]") + * @Form\SerializedName("sites") + */ + public $sites; + + /** + * @var string[] + * + * @Form\Type("string[]") + * @Form\SerializedName("paymentTypes") + */ + public $paymentTypes; + + /** + * @var string[] + * + * @Form\Type("string[]") + * @Form\SerializedName("deliveryTypes") + */ + public $deliveryTypes; + + /** + * @var string[] + * + * @Form\Type("string[]") + * @Form\SerializedName("orderMethods") + */ + public $orderMethods; + + /** + * @var int[] + * + * @Form\Type("int[]") + * @Form\SerializedName("couriers") + */ + public $couriers; + + /** + * @var string[] + * + * @Form\Type("string[]") + * @Form\SerializedName("managerGroups") + */ + public $managerGroups; + + /** + * @var string[] + * + * @Form\Type("string[]") + * @Form\SerializedName("shipmentStores") + */ + public $shipmentStores; + + /** + * @var int[] + * + * @Form\Type("int[]") + * @Form\SerializedName("mgChannels") + */ + public $mgChannels; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("createdAtFrom") + */ + public $createdAtFrom; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("createdAtTo") + */ + public $createdAtTo; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("fullPaidAtFrom") + */ + public $fullPaidAtFrom; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("fullPaidAtTo") + */ + public $fullPaidAtTo; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("paidAtFrom") + */ + public $paidAtFrom; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("paidAtTo") + */ + public $paidAtTo; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("deliveryDateFrom") + */ + public $deliveryDateFrom; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("deliveryDateTo") + */ + public $deliveryDateTo; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("statusUpdatedAtFrom") + */ + public $statusUpdatedAtFrom; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("statusUpdatedAtTo") + */ + public $statusUpdatedAtTo; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("firstWebVisitFrom") + */ + public $firstWebVisitFrom; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("firstWebVisitTo") + */ + public $firstWebVisitTo; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("lastWebVisitFrom") + */ + public $lastWebVisitFrom; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("lastWebVisitTo") + */ + public $lastWebVisitTo; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("firstOrderFrom") + */ + public $firstOrderFrom; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("firstOrderTo") + */ + public $firstOrderTo; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("lastOrderFrom") + */ + public $lastOrderFrom; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("lastOrderTo") + */ + public $lastOrderTo; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("shipmentDateFrom") + */ + public $shipmentDateFrom; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("shipmentDateTo") + */ + public $shipmentDateTo; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("dpdParcelDateFrom") + */ + public $dpdParcelDateFrom; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("dpdParcelDateTo") + */ + public $dpdParcelDateTo; + + /** + * @var array + * + * @Form\Type("array") + * @Form\SerializedName("customFields") + */ + public $customFields; + + /** + * @var string[] + * + * @Form\Type("string[]") + * @Form\SerializedName("extendedStatus") + */ + public $extendedStatus; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("sourceName") + */ + public $sourceName; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("mediumName") + */ + public $mediumName; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("campaignName") + */ + public $campaignName; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("keywordName") + */ + public $keywordName; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("adContentName") + */ + public $adContentName; +} diff --git a/src/Model/Filter/Orders/OrderHistoryFilterV4Type.php b/src/Model/Filter/Orders/OrderHistoryFilterV4Type.php new file mode 100644 index 0000000..a74f862 --- /dev/null +++ b/src/Model/Filter/Orders/OrderHistoryFilterV4Type.php @@ -0,0 +1,62 @@ +") + * @Form\SerializedName("startDate") + */ + public $startDate; + + /** + * @var DateTime|null + * + * @Form\Type("DateTime<'Y-m-d H:i:s'>") + * @Form\SerializedName("endDate") + */ + public $endDate; +} diff --git a/src/Model/Filter/Packs/OrderProductPackFilter.php b/src/Model/Filter/Packs/OrderProductPackFilter.php new file mode 100644 index 0000000..d39b748 --- /dev/null +++ b/src/Model/Filter/Packs/OrderProductPackFilter.php @@ -0,0 +1,109 @@ +") + * @Form\SerializedName("startDate") + */ + public $startDate; + + /** + * @var DateTime + * + * @Form\Type("DateTime<'Y-m-d H:i:s'>") + * @Form\SerializedName("endDate") + */ + public $endDate; +} diff --git a/src/Model/Filter/Segments/SegmentApiFilter.php b/src/Model/Filter/Segments/SegmentApiFilter.php new file mode 100644 index 0000000..ac9f0e7 --- /dev/null +++ b/src/Model/Filter/Segments/SegmentApiFilter.php @@ -0,0 +1,85 @@ +") + * @Form\SerializedName("sinceUpdatedAt") + */ + public $sinceUpdatedAt; +} diff --git a/src/Model/Filter/Store/ProductGroupFilterType.php b/src/Model/Filter/Store/ProductGroupFilterType.php new file mode 100644 index 0000000..eaf5284 --- /dev/null +++ b/src/Model/Filter/Store/ProductGroupFilterType.php @@ -0,0 +1,61 @@ +") + * @JMS\SerializedName("sites") + */ + public $sites; + + /** + * @var \RetailCrm\Api\Model\Filter\Store\ProductPropertyGroup[] + * + * @JMS\Type("array") + * @JMS\SerializedName("groups") + */ + public $groups; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("code") + */ + public $code; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("name") + */ + public $name; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("isNumeric") + */ + public $isNumeric; +} diff --git a/src/Model/Filter/Store/ProductPropertyGroup.php b/src/Model/Filter/Store/ProductPropertyGroup.php new file mode 100644 index 0000000..f2467b1 --- /dev/null +++ b/src/Model/Filter/Store/ProductPropertyGroup.php @@ -0,0 +1,37 @@ +") + * @Form\SerializedName("createdAtFrom") + */ + public $createdAtFrom; + + /** + * @var DateTime + * + * @Form\Type("DateTime<'Y-m-d H:i:s'>") + * @Form\SerializedName("createdAtTo") + */ + public $createdAtTo; +} diff --git a/src/Model/Request/BySiteRequest.php b/src/Model/Request/BySiteRequest.php new file mode 100644 index 0000000..9e2b5b3 --- /dev/null +++ b/src/Model/Request/BySiteRequest.php @@ -0,0 +1,55 @@ +by = $by; + } + + if ('' !== $site) { + $this->site = $site; + } + } +} diff --git a/src/Model/Request/Costs/CostsCreateRequest.php b/src/Model/Request/Costs/CostsCreateRequest.php new file mode 100644 index 0000000..1770869 --- /dev/null +++ b/src/Model/Request/Costs/CostsCreateRequest.php @@ -0,0 +1,39 @@ +") + * @Form\SerializedName("ids") + */ + public $ids = []; +} diff --git a/src/Model/Request/Costs/CostsEditRequest.php b/src/Model/Request/Costs/CostsEditRequest.php new file mode 100644 index 0000000..f1697a4 --- /dev/null +++ b/src/Model/Request/Costs/CostsEditRequest.php @@ -0,0 +1,20 @@ +") + * @Form\SerializedName("costs") + */ + public $costs; +} diff --git a/src/Model/Request/CustomFields/CustomDictionaryCreateRequest.php b/src/Model/Request/CustomFields/CustomDictionaryCreateRequest.php new file mode 100644 index 0000000..7fa5e65 --- /dev/null +++ b/src/Model/Request/CustomFields/CustomDictionaryCreateRequest.php @@ -0,0 +1,44 @@ +customDictionary = $customDictionary; + } + } +} diff --git a/src/Model/Request/CustomFields/CustomFieldsCreateRequest.php b/src/Model/Request/CustomFields/CustomFieldsCreateRequest.php new file mode 100644 index 0000000..4ce6895 --- /dev/null +++ b/src/Model/Request/CustomFields/CustomFieldsCreateRequest.php @@ -0,0 +1,43 @@ +customField = $customField; + } + } +} diff --git a/src/Model/Request/CustomFields/CustomFieldsDictionariesRequest.php b/src/Model/Request/CustomFields/CustomFieldsDictionariesRequest.php new file mode 100644 index 0000000..03621c2 --- /dev/null +++ b/src/Model/Request/CustomFields/CustomFieldsDictionariesRequest.php @@ -0,0 +1,33 @@ +") + * @Form\SerializedName("customers") + * @Form\JsonField() + */ + public $customers; +} diff --git a/src/Model/Request/Customers/CustomersHistoryRequest.php b/src/Model/Request/Customers/CustomersHistoryRequest.php new file mode 100644 index 0000000..547a271 --- /dev/null +++ b/src/Model/Request/Customers/CustomersHistoryRequest.php @@ -0,0 +1,33 @@ +") + * @Form\SerializedName("customers") + * @Form\JsonField() + */ + public $customers; +} diff --git a/src/Model/Request/CustomersCorporate/CustomersCorporateAddressesCreateRequest.php b/src/Model/Request/CustomersCorporate/CustomersCorporateAddressesCreateRequest.php new file mode 100644 index 0000000..25e36b1 --- /dev/null +++ b/src/Model/Request/CustomersCorporate/CustomersCorporateAddressesCreateRequest.php @@ -0,0 +1,31 @@ +") + * @Form\SerializedName("customersCorporate") + * @Form\JsonField() + */ + public $customersCorporate; +} diff --git a/src/Model/Request/CustomersCorporate/CustomersCorporateRequest.php b/src/Model/Request/CustomersCorporate/CustomersCorporateRequest.php new file mode 100644 index 0000000..ef4cf9c --- /dev/null +++ b/src/Model/Request/CustomersCorporate/CustomersCorporateRequest.php @@ -0,0 +1,33 @@ +") + * @Form\SerializedName("customersCorporate") + * @Form\JsonField() + */ + public $customersCorporate; +} diff --git a/src/Model/Request/Delivery/DeliveryCalculateRequest.php b/src/Model/Request/Delivery/DeliveryCalculateRequest.php new file mode 100644 index 0000000..95fd931 --- /dev/null +++ b/src/Model/Request/Delivery/DeliveryCalculateRequest.php @@ -0,0 +1,40 @@ +") + * @Form\SerializedName("statusUpdate") + * @Form\JsonField() + */ + public $statusUpdate; +} diff --git a/src/Model/Request/Files/FilesEditRequest.php b/src/Model/Request/Files/FilesEditRequest.php new file mode 100644 index 0000000..32a30fe --- /dev/null +++ b/src/Model/Request/Files/FilesEditRequest.php @@ -0,0 +1,31 @@ +file = $file; + } +} diff --git a/src/Model/Request/Integration/IntegrationModulesEditRequest.php b/src/Model/Request/Integration/IntegrationModulesEditRequest.php new file mode 100644 index 0000000..c0928c6 --- /dev/null +++ b/src/Model/Request/Integration/IntegrationModulesEditRequest.php @@ -0,0 +1,42 @@ +integrationModule = $integrationModule; + } +} diff --git a/src/Model/Request/Loyalty/LoyaltiesRequest.php b/src/Model/Request/Loyalty/LoyaltiesRequest.php new file mode 100644 index 0000000..61a439a --- /dev/null +++ b/src/Model/Request/Loyalty/LoyaltiesRequest.php @@ -0,0 +1,33 @@ +loyaltyAccount = $loyaltyAccount; + } +} diff --git a/src/Model/Request/Loyalty/LoyaltyAccountsRequest.php b/src/Model/Request/Loyalty/LoyaltyAccountsRequest.php new file mode 100644 index 0000000..cc2893c --- /dev/null +++ b/src/Model/Request/Loyalty/LoyaltyAccountsRequest.php @@ -0,0 +1,33 @@ +") + * @Form\SerializedName("activationDate") + */ + public $activationDate; + + /** + * @var DateTime + * + * @Form\Type("DateTime<'Y-m-d'>") + * @Form\SerializedName("expireDate") + */ + public $expireDate; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("comment") + */ + public $comment; +} diff --git a/src/Model/Request/Loyalty/LoyaltyBonusOperationsRequest.php b/src/Model/Request/Loyalty/LoyaltyBonusOperationsRequest.php new file mode 100644 index 0000000..b42b0dc --- /dev/null +++ b/src/Model/Request/Loyalty/LoyaltyBonusOperationsRequest.php @@ -0,0 +1,33 @@ +") + * @Form\SerializedName("orders") + * @Form\JsonField() + */ + public $orders; + + /** + * OrdersFixExternalIdsRequest constructor. + * + * @param \RetailCrm\Api\Model\Entity\FixExternalRow[] $orders + */ + public function __construct(array $orders = []) + { + $this->orders = $orders; + } +} diff --git a/src/Model/Request/Orders/OrdersHistoryRequest.php b/src/Model/Request/Orders/OrdersHistoryRequest.php new file mode 100644 index 0000000..51fdffe --- /dev/null +++ b/src/Model/Request/Orders/OrdersHistoryRequest.php @@ -0,0 +1,33 @@ +") + * @Form\SerializedName("orders") + * @Form\JsonField() + */ + public $orders; +} diff --git a/src/Model/Request/Packs/PacksCreateRequest.php b/src/Model/Request/Packs/PacksCreateRequest.php new file mode 100644 index 0000000..b614124 --- /dev/null +++ b/src/Model/Request/Packs/PacksCreateRequest.php @@ -0,0 +1,44 @@ +pack = $pack; + } + } +} diff --git a/src/Model/Request/Packs/PacksHistoryRequest.php b/src/Model/Request/Packs/PacksHistoryRequest.php new file mode 100644 index 0000000..95b8b25 --- /dev/null +++ b/src/Model/Request/Packs/PacksHistoryRequest.php @@ -0,0 +1,33 @@ +check = $check; + } + } +} diff --git a/src/Model/Request/Payments/PaymentCreateInvoiceRequest.php b/src/Model/Request/Payments/PaymentCreateInvoiceRequest.php new file mode 100644 index 0000000..9af440d --- /dev/null +++ b/src/Model/Request/Payments/PaymentCreateInvoiceRequest.php @@ -0,0 +1,44 @@ +createInvoice = $createInvoice; + } + } +} diff --git a/src/Model/Request/Payments/PaymentUpdateInvoiceRequest.php b/src/Model/Request/Payments/PaymentUpdateInvoiceRequest.php new file mode 100644 index 0000000..3337322 --- /dev/null +++ b/src/Model/Request/Payments/PaymentUpdateInvoiceRequest.php @@ -0,0 +1,44 @@ +updateInvoice = $updateInvoice; + } + } +} diff --git a/src/Model/Request/References/CostGroupsEditRequest.php b/src/Model/Request/References/CostGroupsEditRequest.php new file mode 100644 index 0000000..bb52745 --- /dev/null +++ b/src/Model/Request/References/CostGroupsEditRequest.php @@ -0,0 +1,44 @@ +costGroup = $costGroup; + } + } +} diff --git a/src/Model/Request/References/CostItemsEditRequest.php b/src/Model/Request/References/CostItemsEditRequest.php new file mode 100644 index 0000000..4d4ed26 --- /dev/null +++ b/src/Model/Request/References/CostItemsEditRequest.php @@ -0,0 +1,44 @@ +costItem = $costItem; + } + } +} diff --git a/src/Model/Request/References/CouriersCreateRequest.php b/src/Model/Request/References/CouriersCreateRequest.php new file mode 100644 index 0000000..ca72d4d --- /dev/null +++ b/src/Model/Request/References/CouriersCreateRequest.php @@ -0,0 +1,44 @@ +courier = $courier; + } + } +} diff --git a/src/Model/Request/References/DeliveryServicesEditRequest.php b/src/Model/Request/References/DeliveryServicesEditRequest.php new file mode 100644 index 0000000..977864e --- /dev/null +++ b/src/Model/Request/References/DeliveryServicesEditRequest.php @@ -0,0 +1,44 @@ +deliveryService = $deliveryService; + } + } +} diff --git a/src/Model/Request/References/DeliveryTypesEditRequest.php b/src/Model/Request/References/DeliveryTypesEditRequest.php new file mode 100644 index 0000000..c5b96b5 --- /dev/null +++ b/src/Model/Request/References/DeliveryTypesEditRequest.php @@ -0,0 +1,44 @@ +deliveryType = $deliveryType; + } + } +} diff --git a/src/Model/Request/References/LegalEntityEditRequest.php b/src/Model/Request/References/LegalEntityEditRequest.php new file mode 100644 index 0000000..2b91e0d --- /dev/null +++ b/src/Model/Request/References/LegalEntityEditRequest.php @@ -0,0 +1,44 @@ +legalEntity = $legalEntity; + } + } +} diff --git a/src/Model/Request/References/OrderMethodsEditRequest.php b/src/Model/Request/References/OrderMethodsEditRequest.php new file mode 100644 index 0000000..a9012f7 --- /dev/null +++ b/src/Model/Request/References/OrderMethodsEditRequest.php @@ -0,0 +1,44 @@ +orderMethod = $orderMethod; + } + } +} diff --git a/src/Model/Request/References/OrderTypesEditRequest.php b/src/Model/Request/References/OrderTypesEditRequest.php new file mode 100644 index 0000000..daf2896 --- /dev/null +++ b/src/Model/Request/References/OrderTypesEditRequest.php @@ -0,0 +1,44 @@ +orderType = $orderType; + } + } +} diff --git a/src/Model/Request/References/PaymentStatusesEditRequest.php b/src/Model/Request/References/PaymentStatusesEditRequest.php new file mode 100644 index 0000000..e4b2a5e --- /dev/null +++ b/src/Model/Request/References/PaymentStatusesEditRequest.php @@ -0,0 +1,44 @@ +paymentStatus = $paymentStatus; + } + } +} diff --git a/src/Model/Request/References/PaymentTypesEditRequest.php b/src/Model/Request/References/PaymentTypesEditRequest.php new file mode 100644 index 0000000..496e5ec --- /dev/null +++ b/src/Model/Request/References/PaymentTypesEditRequest.php @@ -0,0 +1,44 @@ +paymentType = $paymentType; + } + } +} diff --git a/src/Model/Request/References/PriceTypesEditRequest.php b/src/Model/Request/References/PriceTypesEditRequest.php new file mode 100644 index 0000000..426d93f --- /dev/null +++ b/src/Model/Request/References/PriceTypesEditRequest.php @@ -0,0 +1,44 @@ +priceType = $priceType; + } + } +} diff --git a/src/Model/Request/References/ProductStatusesEditRequest.php b/src/Model/Request/References/ProductStatusesEditRequest.php new file mode 100644 index 0000000..e34a571 --- /dev/null +++ b/src/Model/Request/References/ProductStatusesEditRequest.php @@ -0,0 +1,44 @@ +productStatus = $productStatus; + } + } +} diff --git a/src/Model/Request/References/SitesEditRequest.php b/src/Model/Request/References/SitesEditRequest.php new file mode 100644 index 0000000..2c61f3c --- /dev/null +++ b/src/Model/Request/References/SitesEditRequest.php @@ -0,0 +1,44 @@ +site = $site; + } + } +} diff --git a/src/Model/Request/References/StatusesEditRequest.php b/src/Model/Request/References/StatusesEditRequest.php new file mode 100644 index 0000000..d445df3 --- /dev/null +++ b/src/Model/Request/References/StatusesEditRequest.php @@ -0,0 +1,44 @@ +status = $status; + } + } +} diff --git a/src/Model/Request/References/StoresEditRequest.php b/src/Model/Request/References/StoresEditRequest.php new file mode 100644 index 0000000..980dfe5 --- /dev/null +++ b/src/Model/Request/References/StoresEditRequest.php @@ -0,0 +1,44 @@ +store = $store; + } + } +} diff --git a/src/Model/Request/References/UnitsEditRequest.php b/src/Model/Request/References/UnitsEditRequest.php new file mode 100644 index 0000000..f59f3ce --- /dev/null +++ b/src/Model/Request/References/UnitsEditRequest.php @@ -0,0 +1,44 @@ +unit = $unit; + } + } +} diff --git a/src/Model/Request/Segments/SegmentsRequest.php b/src/Model/Request/Segments/SegmentsRequest.php new file mode 100644 index 0000000..2d7cd0c --- /dev/null +++ b/src/Model/Request/Segments/SegmentsRequest.php @@ -0,0 +1,33 @@ +prices = $prices; + } + } +} diff --git a/src/Model/Request/Store/ProductGroupsRequest.php b/src/Model/Request/Store/ProductGroupsRequest.php new file mode 100644 index 0000000..4c68930 --- /dev/null +++ b/src/Model/Request/Store/ProductGroupsRequest.php @@ -0,0 +1,33 @@ +event = $event; + } + } +} diff --git a/src/Model/Request/Telephony/TelephonyCallsUploadRequest.php b/src/Model/Request/Telephony/TelephonyCallsUploadRequest.php new file mode 100644 index 0000000..528768f --- /dev/null +++ b/src/Model/Request/Telephony/TelephonyCallsUploadRequest.php @@ -0,0 +1,43 @@ +") + * @Form\SerializedName("calls") + * @Form\JsonField() + */ + public $calls; + + /** + * TelephonyCallsUploadRequest constructor. + * + * @param \RetailCrm\Api\Model\Entity\Telephony\Call[]|null $calls + */ + public function __construct(?array $calls = null) + { + if (null !== $calls) { + $this->calls = $calls; + } + } +} diff --git a/src/Model/Request/Telephony/TelephonyManagerRequest.php b/src/Model/Request/Telephony/TelephonyManagerRequest.php new file mode 100644 index 0000000..f79817e --- /dev/null +++ b/src/Model/Request/Telephony/TelephonyManagerRequest.php @@ -0,0 +1,46 @@ +status = $status; + } + } +} diff --git a/src/Model/Request/Verification/SmsVerificationConfirmRequest.php b/src/Model/Request/Verification/SmsVerificationConfirmRequest.php new file mode 100644 index 0000000..fdbff46 --- /dev/null +++ b/src/Model/Request/Verification/SmsVerificationConfirmRequest.php @@ -0,0 +1,44 @@ +verification = $verification; + } + } +} diff --git a/src/Model/RequestData.php b/src/Model/RequestData.php new file mode 100644 index 0000000..82890d6 --- /dev/null +++ b/src/Model/RequestData.php @@ -0,0 +1,47 @@ +method = $method; + $this->uri = $uri; + $this->requestModel = $requestModel; + } +} diff --git a/src/Model/Response/AbstractPaginatedResponse.php b/src/Model/Response/AbstractPaginatedResponse.php new file mode 100644 index 0000000..6024a40 --- /dev/null +++ b/src/Model/Response/AbstractPaginatedResponse.php @@ -0,0 +1,32 @@ +") + * @JMS\SerializedName("versions") + */ + public $versions; +} diff --git a/src/Model/Response/Api/Credentials.php b/src/Model/Response/Api/Credentials.php new file mode 100644 index 0000000..fc83016 --- /dev/null +++ b/src/Model/Response/Api/Credentials.php @@ -0,0 +1,46 @@ +") + * @JMS\SerializedName("credentials") + */ + public $credentials; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("siteAccess") + */ + public $siteAccess; + + /** + * @var string[] + * + * @JMS\Type("array") + * @JMS\SerializedName("sitesAvailable") + */ + public $sitesAvailable; +} diff --git a/src/Model/Response/Costs/CostsDeleteResponse.php b/src/Model/Response/Costs/CostsDeleteResponse.php new file mode 100644 index 0000000..7d66798 --- /dev/null +++ b/src/Model/Response/Costs/CostsDeleteResponse.php @@ -0,0 +1,38 @@ +") + * @JMS\SerializedName("notRemovedIds") + */ + public $notRemovedIds; +} diff --git a/src/Model/Response/Costs/CostsGetResponse.php b/src/Model/Response/Costs/CostsGetResponse.php new file mode 100644 index 0000000..543480d --- /dev/null +++ b/src/Model/Response/Costs/CostsGetResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("costs") + */ + public $costs; +} diff --git a/src/Model/Response/Costs/CostsUploadResponse.php b/src/Model/Response/Costs/CostsUploadResponse.php new file mode 100644 index 0000000..5a459cf --- /dev/null +++ b/src/Model/Response/Costs/CostsUploadResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("uploadedCosts") + */ + public $uploadedCosts; +} diff --git a/src/Model/Response/CustomFields/CustomDictionaryCreateResponse.php b/src/Model/Response/CustomFields/CustomDictionaryCreateResponse.php new file mode 100644 index 0000000..89ea9e3 --- /dev/null +++ b/src/Model/Response/CustomFields/CustomDictionaryCreateResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("customDictionaries") + */ + public $customDictionaries; +} diff --git a/src/Model/Response/CustomFields/CustomFieldsEditResponse.php b/src/Model/Response/CustomFields/CustomFieldsEditResponse.php new file mode 100644 index 0000000..236f3ec --- /dev/null +++ b/src/Model/Response/CustomFields/CustomFieldsEditResponse.php @@ -0,0 +1,20 @@ +") + * @JMS\SerializedName("customFields") + */ + public $customFields; +} diff --git a/src/Model/Response/Customers/CustomerNotesResponse.php b/src/Model/Response/Customers/CustomerNotesResponse.php new file mode 100644 index 0000000..2009b28 --- /dev/null +++ b/src/Model/Response/Customers/CustomerNotesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("notes") + */ + public $notes; +} diff --git a/src/Model/Response/Customers/CustomersEditResponse.php b/src/Model/Response/Customers/CustomersEditResponse.php new file mode 100644 index 0000000..1091eaa --- /dev/null +++ b/src/Model/Response/Customers/CustomersEditResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("generatedAt") + */ + public $generatedAt; + + /** + * @var \RetailCrm\Api\Model\Entity\Customers\CustomerHistory[] + * + * @JMS\Type("array") + * @JMS\SerializedName("history") + */ + public $history; +} diff --git a/src/Model/Response/Customers/CustomersResponse.php b/src/Model/Response/Customers/CustomersResponse.php new file mode 100644 index 0000000..c4c1885 --- /dev/null +++ b/src/Model/Response/Customers/CustomersResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("customers") + */ + public $customers; +} diff --git a/src/Model/Response/Customers/CustomersUploadResponse.php b/src/Model/Response/Customers/CustomersUploadResponse.php new file mode 100644 index 0000000..5b095f3 --- /dev/null +++ b/src/Model/Response/Customers/CustomersUploadResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("uploadedCustomers") + */ + public $uploadedCustomers; +} diff --git a/src/Model/Response/CustomersCorporate/CustomersCorporateAddressesResponse.php b/src/Model/Response/CustomersCorporate/CustomersCorporateAddressesResponse.php new file mode 100644 index 0000000..47292e8 --- /dev/null +++ b/src/Model/Response/CustomersCorporate/CustomersCorporateAddressesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("addresses") + */ + public $addresses; +} diff --git a/src/Model/Response/CustomersCorporate/CustomersCorporateCompaniesResponse.php b/src/Model/Response/CustomersCorporate/CustomersCorporateCompaniesResponse.php new file mode 100644 index 0000000..6ab5ce4 --- /dev/null +++ b/src/Model/Response/CustomersCorporate/CustomersCorporateCompaniesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("companies") + */ + public $companies; +} diff --git a/src/Model/Response/CustomersCorporate/CustomersCorporateContactsResponse.php b/src/Model/Response/CustomersCorporate/CustomersCorporateContactsResponse.php new file mode 100644 index 0000000..d85a5fb --- /dev/null +++ b/src/Model/Response/CustomersCorporate/CustomersCorporateContactsResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("contacts") + */ + public $contacts; +} diff --git a/src/Model/Response/CustomersCorporate/CustomersCorporateGetResponse.php b/src/Model/Response/CustomersCorporate/CustomersCorporateGetResponse.php new file mode 100644 index 0000000..3c1d1d9 --- /dev/null +++ b/src/Model/Response/CustomersCorporate/CustomersCorporateGetResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("generatedAt") + */ + public $generatedAt; + + /** + * @var \RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporateHistory[] + * + * @JMS\Type("array") + * @JMS\SerializedName("history") + */ + public $history; +} diff --git a/src/Model/Response/CustomersCorporate/CustomersCorporateResponse.php b/src/Model/Response/CustomersCorporate/CustomersCorporateResponse.php new file mode 100644 index 0000000..31415be --- /dev/null +++ b/src/Model/Response/CustomersCorporate/CustomersCorporateResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("customersCorporate") + */ + public $customersCorporate; +} diff --git a/src/Model/Response/Delivery/DeliveryCalculateResponse.php b/src/Model/Response/Delivery/DeliveryCalculateResponse.php new file mode 100644 index 0000000..789fee6 --- /dev/null +++ b/src/Model/Response/Delivery/DeliveryCalculateResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("calculations") + */ + public $calculations; +} diff --git a/src/Model/Response/Delivery/DeliveryShipmentsCreateResponse.php b/src/Model/Response/Delivery/DeliveryShipmentsCreateResponse.php new file mode 100644 index 0000000..9605cb8 --- /dev/null +++ b/src/Model/Response/Delivery/DeliveryShipmentsCreateResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("deliveryShipments") + */ + public $deliveryShipments; +} diff --git a/src/Model/Response/ErrorResponse.php b/src/Model/Response/ErrorResponse.php new file mode 100644 index 0000000..bc0a18f --- /dev/null +++ b/src/Model/Response/ErrorResponse.php @@ -0,0 +1,45 @@ +success = false; + } + + /** + * @var string[] + * + * @JMS\Type("array") + * @JMS\SerializedName("errors") + */ + public $errors; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("errorMsg") + */ + public $errorMsg; +} diff --git a/src/Model/Response/Files/FilesDownloadResponse.php b/src/Model/Response/Files/FilesDownloadResponse.php new file mode 100644 index 0000000..7950c6e --- /dev/null +++ b/src/Model/Response/Files/FilesDownloadResponse.php @@ -0,0 +1,40 @@ +fileName = $fileName; + $this->data = $data; + } +} diff --git a/src/Model/Response/Files/FilesGetResponse.php b/src/Model/Response/Files/FilesGetResponse.php new file mode 100644 index 0000000..cf30b33 --- /dev/null +++ b/src/Model/Response/Files/FilesGetResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("files") + */ + public $files; +} diff --git a/src/Model/Response/Files/FilesUploadResponse.php b/src/Model/Response/Files/FilesUploadResponse.php new file mode 100644 index 0000000..1b225d4 --- /dev/null +++ b/src/Model/Response/Files/FilesUploadResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("loyalties") + */ + public $loyalties; +} diff --git a/src/Model/Response/Loyalty/LoyaltyAccountActivateResponse.php b/src/Model/Response/Loyalty/LoyaltyAccountActivateResponse.php new file mode 100644 index 0000000..96e9de2 --- /dev/null +++ b/src/Model/Response/Loyalty/LoyaltyAccountActivateResponse.php @@ -0,0 +1,38 @@ +") + * @JMS\SerializedName("loyaltyAccounts") + */ + public $loyaltyAccounts; +} diff --git a/src/Model/Response/Loyalty/LoyaltyBonusCreditResponse.php b/src/Model/Response/Loyalty/LoyaltyBonusCreditResponse.php new file mode 100644 index 0000000..e564cc8 --- /dev/null +++ b/src/Model/Response/Loyalty/LoyaltyBonusCreditResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("bonusOperations") + */ + public $bonusOperations; +} diff --git a/src/Model/Response/Loyalty/LoyaltyCalculateResponse.php b/src/Model/Response/Loyalty/LoyaltyCalculateResponse.php new file mode 100644 index 0000000..e2eb53c --- /dev/null +++ b/src/Model/Response/Loyalty/LoyaltyCalculateResponse.php @@ -0,0 +1,46 @@ +") + * @JMS\SerializedName("calculations") + */ + public $calculations; + + /** + * @var \RetailCrm\Api\Model\Entity\Loyalty\SerializedLoyalty + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Loyalty\SerializedLoyalty") + * @JMS\SerializedName("loyalty") + */ + public $loyalty; +} diff --git a/src/Model/Response/Orders/OrdersCombineResponse.php b/src/Model/Response/Orders/OrdersCombineResponse.php new file mode 100644 index 0000000..f9a20cc --- /dev/null +++ b/src/Model/Response/Orders/OrdersCombineResponse.php @@ -0,0 +1,30 @@ + + * + * @JMS\Type("array") + * @JMS\SerializedName("errors") + */ + public $errors; +} diff --git a/src/Model/Response/Orders/OrdersCreateResponse.php b/src/Model/Response/Orders/OrdersCreateResponse.php new file mode 100644 index 0000000..f49e929 --- /dev/null +++ b/src/Model/Response/Orders/OrdersCreateResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("generatedAt") + */ + public $generatedAt; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\OrderHistory[] + * + * @JMS\Type("array") + * @JMS\SerializedName("history") + */ + public $history; +} diff --git a/src/Model/Response/Orders/OrdersLoyaltyApplyResponse.php b/src/Model/Response/Orders/OrdersLoyaltyApplyResponse.php new file mode 100644 index 0000000..a50a005 --- /dev/null +++ b/src/Model/Response/Orders/OrdersLoyaltyApplyResponse.php @@ -0,0 +1,38 @@ +") + * @JMS\SerializedName("orders") + */ + public $orders; +} diff --git a/src/Model/Response/Orders/OrdersStatusesResponse.php b/src/Model/Response/Orders/OrdersStatusesResponse.php new file mode 100644 index 0000000..5a82947 --- /dev/null +++ b/src/Model/Response/Orders/OrdersStatusesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("orders") + */ + public $orders; +} diff --git a/src/Model/Response/Orders/OrdersUploadResponse.php b/src/Model/Response/Orders/OrdersUploadResponse.php new file mode 100644 index 0000000..29bf684 --- /dev/null +++ b/src/Model/Response/Orders/OrdersUploadResponse.php @@ -0,0 +1,46 @@ +") + * @JMS\SerializedName("uploadedOrders") + */ + public $uploadedOrders; + + /** + * @var \RetailCrm\Api\Model\Entity\Orders\Order[] + * + * @JMS\Type("array") + * @JMS\SerializedName("orders") + */ + public $orders; +} diff --git a/src/Model/Response/Packs/PacksGetResponse.php b/src/Model/Response/Packs/PacksGetResponse.php new file mode 100644 index 0000000..42172db --- /dev/null +++ b/src/Model/Response/Packs/PacksGetResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("generatedAt") + */ + public $generatedAt; + + /** + * @var \RetailCrm\Api\Model\Entity\Packs\OrderProductPackHistory[] + * + * @JMS\Type("array") + * @JMS\SerializedName("history") + */ + public $history; +} diff --git a/src/Model/Response/Packs/PacksResponse.php b/src/Model/Response/Packs/PacksResponse.php new file mode 100644 index 0000000..09d70c6 --- /dev/null +++ b/src/Model/Response/Packs/PacksResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("packs") + */ + public $packs; +} diff --git a/src/Model/Response/Payments/PaymentCheckResponse.php b/src/Model/Response/Payments/PaymentCheckResponse.php new file mode 100644 index 0000000..5a84079 --- /dev/null +++ b/src/Model/Response/Payments/PaymentCheckResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("costGroups") + */ + public $costGroups; +} diff --git a/src/Model/Response/References/CostItemsResponse.php b/src/Model/Response/References/CostItemsResponse.php new file mode 100644 index 0000000..49f04c6 --- /dev/null +++ b/src/Model/Response/References/CostItemsResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("costItems") + */ + public $costItems; +} diff --git a/src/Model/Response/References/CountriesResponse.php b/src/Model/Response/References/CountriesResponse.php new file mode 100644 index 0000000..0e41d2d --- /dev/null +++ b/src/Model/Response/References/CountriesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("countriesIso") + */ + public $countriesIso; +} diff --git a/src/Model/Response/References/CouriersResponse.php b/src/Model/Response/References/CouriersResponse.php new file mode 100644 index 0000000..1e25292 --- /dev/null +++ b/src/Model/Response/References/CouriersResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("couriers") + */ + public $couriers; +} diff --git a/src/Model/Response/References/DeliveryServicesResponse.php b/src/Model/Response/References/DeliveryServicesResponse.php new file mode 100644 index 0000000..ca032c7 --- /dev/null +++ b/src/Model/Response/References/DeliveryServicesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("deliveryServices") + */ + public $deliveryServices; +} diff --git a/src/Model/Response/References/DeliveryTypesResponse.php b/src/Model/Response/References/DeliveryTypesResponse.php new file mode 100644 index 0000000..14af4a2 --- /dev/null +++ b/src/Model/Response/References/DeliveryTypesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("deliveryTypes") + */ + public $deliveryTypes; +} diff --git a/src/Model/Response/References/LegalEntitiesResponse.php b/src/Model/Response/References/LegalEntitiesResponse.php new file mode 100644 index 0000000..b95c60a --- /dev/null +++ b/src/Model/Response/References/LegalEntitiesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("legalEntities") + */ + public $legalEntities; +} diff --git a/src/Model/Response/References/MgChannelsResponse.php b/src/Model/Response/References/MgChannelsResponse.php new file mode 100644 index 0000000..9220dfd --- /dev/null +++ b/src/Model/Response/References/MgChannelsResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("mgChannels") + */ + public $mgChannels; +} diff --git a/src/Model/Response/References/OrderMethodsResponse.php b/src/Model/Response/References/OrderMethodsResponse.php new file mode 100644 index 0000000..e35c731 --- /dev/null +++ b/src/Model/Response/References/OrderMethodsResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("orderMethods") + */ + public $orderMethods; +} diff --git a/src/Model/Response/References/OrderTypesResponse.php b/src/Model/Response/References/OrderTypesResponse.php new file mode 100644 index 0000000..2f038d9 --- /dev/null +++ b/src/Model/Response/References/OrderTypesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("orderTypes") + */ + public $orderTypes; +} diff --git a/src/Model/Response/References/PaymentStatusesResponse.php b/src/Model/Response/References/PaymentStatusesResponse.php new file mode 100644 index 0000000..adce869 --- /dev/null +++ b/src/Model/Response/References/PaymentStatusesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("paymentStatuses") + */ + public $paymentStatuses; +} diff --git a/src/Model/Response/References/PaymentTypesResponse.php b/src/Model/Response/References/PaymentTypesResponse.php new file mode 100644 index 0000000..9bb837f --- /dev/null +++ b/src/Model/Response/References/PaymentTypesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("paymentTypes") + */ + public $paymentTypes; +} diff --git a/src/Model/Response/References/PriceTypesResponse.php b/src/Model/Response/References/PriceTypesResponse.php new file mode 100644 index 0000000..632b25b --- /dev/null +++ b/src/Model/Response/References/PriceTypesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("priceTypes") + */ + public $priceTypes; +} diff --git a/src/Model/Response/References/ProductStatusesResponse.php b/src/Model/Response/References/ProductStatusesResponse.php new file mode 100644 index 0000000..25e1b24 --- /dev/null +++ b/src/Model/Response/References/ProductStatusesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("productStatuses") + */ + public $productStatuses; +} diff --git a/src/Model/Response/References/SitesResponse.php b/src/Model/Response/References/SitesResponse.php new file mode 100644 index 0000000..b47ca79 --- /dev/null +++ b/src/Model/Response/References/SitesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("sites") + */ + public $sites; +} diff --git a/src/Model/Response/References/StatusGroupsResponse.php b/src/Model/Response/References/StatusGroupsResponse.php new file mode 100644 index 0000000..16494f6 --- /dev/null +++ b/src/Model/Response/References/StatusGroupsResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("statusGroups") + */ + public $statusGroups; +} diff --git a/src/Model/Response/References/StatusesResponse.php b/src/Model/Response/References/StatusesResponse.php new file mode 100644 index 0000000..52e47c3 --- /dev/null +++ b/src/Model/Response/References/StatusesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("statuses") + */ + public $statuses; +} diff --git a/src/Model/Response/References/StoresResponse.php b/src/Model/Response/References/StoresResponse.php new file mode 100644 index 0000000..28bcd20 --- /dev/null +++ b/src/Model/Response/References/StoresResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("stores") + */ + public $stores; +} diff --git a/src/Model/Response/References/UnitsResponse.php b/src/Model/Response/References/UnitsResponse.php new file mode 100644 index 0000000..9cf0c48 --- /dev/null +++ b/src/Model/Response/References/UnitsResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("units") + */ + public $units; +} diff --git a/src/Model/Response/Segments/SegmentsResponse.php b/src/Model/Response/Segments/SegmentsResponse.php new file mode 100644 index 0000000..2ea8d88 --- /dev/null +++ b/src/Model/Response/Segments/SegmentsResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("segments") + */ + public $segments; +} diff --git a/src/Model/Response/Settings/SettingsResponse.php b/src/Model/Response/Settings/SettingsResponse.php new file mode 100644 index 0000000..5138ae3 --- /dev/null +++ b/src/Model/Response/Settings/SettingsResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("offers") + */ + public $offers; +} diff --git a/src/Model/Response/Store/InventoriesUploadResponse.php b/src/Model/Response/Store/InventoriesUploadResponse.php new file mode 100644 index 0000000..c450bf8 --- /dev/null +++ b/src/Model/Response/Store/InventoriesUploadResponse.php @@ -0,0 +1,38 @@ +") + * @JMS\SerializedName("notFoundOffers") + */ + public $notFoundOffers; +} diff --git a/src/Model/Response/Store/PricesUploadResponse.php b/src/Model/Response/Store/PricesUploadResponse.php new file mode 100644 index 0000000..e17c3ad --- /dev/null +++ b/src/Model/Response/Store/PricesUploadResponse.php @@ -0,0 +1,38 @@ +") + * @JMS\SerializedName("notFoundOffers") + */ + public $notFoundOffers; +} diff --git a/src/Model/Response/Store/ProductGroupsResponse.php b/src/Model/Response/Store/ProductGroupsResponse.php new file mode 100644 index 0000000..7dc3fbb --- /dev/null +++ b/src/Model/Response/Store/ProductGroupsResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("productGroup") + */ + public $productGroup; +} diff --git a/src/Model/Response/Store/ProductPropertiesResponse.php b/src/Model/Response/Store/ProductPropertiesResponse.php new file mode 100644 index 0000000..877d264 --- /dev/null +++ b/src/Model/Response/Store/ProductPropertiesResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("properties") + */ + public $properties; +} diff --git a/src/Model/Response/Store/ProductsResponse.php b/src/Model/Response/Store/ProductsResponse.php new file mode 100644 index 0000000..a820d94 --- /dev/null +++ b/src/Model/Response/Store/ProductsResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("products") + */ + public $products; +} diff --git a/src/Model/Response/SuccessResponse.php b/src/Model/Response/SuccessResponse.php new file mode 100644 index 0000000..efad266 --- /dev/null +++ b/src/Model/Response/SuccessResponse.php @@ -0,0 +1,32 @@ +") + * @JMS\SerializedName("tasks") + */ + public $tasks; +} diff --git a/src/Model/Response/Telephony/CallEventResponse.php b/src/Model/Response/Telephony/CallEventResponse.php new file mode 100644 index 0000000..9414507 --- /dev/null +++ b/src/Model/Response/Telephony/CallEventResponse.php @@ -0,0 +1,38 @@ +") + * @JMS\SerializedName("groups") + */ + public $groups; +} diff --git a/src/Model/Response/Users/UsersGetResponse.php b/src/Model/Response/Users/UsersGetResponse.php new file mode 100644 index 0000000..ed4f587 --- /dev/null +++ b/src/Model/Response/Users/UsersGetResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("users") + */ + public $users; +} diff --git a/src/Model/Response/Verification/SmsVerificationConfirmResponse.php b/src/Model/Response/Verification/SmsVerificationConfirmResponse.php new file mode 100644 index 0000000..b88500c --- /dev/null +++ b/src/Model/Response/Verification/SmsVerificationConfirmResponse.php @@ -0,0 +1,30 @@ +baseUrl = $baseUrl; + $this->request = $request; + $this->response = $response; + $this->type = $type; + } +} diff --git a/src/ResourceGroup/AbstractApiResourceGroup.php b/src/ResourceGroup/AbstractApiResourceGroup.php new file mode 100644 index 0000000..cd52ef3 --- /dev/null +++ b/src/ResourceGroup/AbstractApiResourceGroup.php @@ -0,0 +1,184 @@ +baseUrl = $baseUrl; + $this->httpClient = $httpClient; + $this->requestTransformer = $requestTransformer; + $this->responseTransformer = $responseTransformer; + $this->eventDispatcher = $eventDispatcher; + $this->logger = $logger; + } + + /** + * Sends request to provided route with provided method and body, returns response of provided type. + * Request will be put into GET parameters or into POST form-data (depends on method). + * + * Note: do not remove "useless" exceptions which are marked as "never thrown" by IDE. + * PSR-18's ClientInterface doesn't have them in the DocBlock, but, according to PSR-18, + * they can be thrown by clients, and therefore should be present here. + * + * @see https://www.php-fig.org/psr/psr-18/#error-handling + * + * @param string $method + * @param string $route + * @param \RetailCrm\Api\Interfaces\RequestInterface|null $request + * @param string $type + * + * @return \RetailCrm\Api\Interfaces\ResponseInterface + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + protected function sendRequest( + string $method, + string $route, + ?RequestInterface $request, + string $type + ): ResponseInterface { + $method = strtoupper($method); + $psrRequest = $this->requestTransformer->createPsrRequest( + $method, + $this->route($route), + $request + ); + + if ($this->logger instanceof LoggerInterface && !($this->logger instanceof NullLogger)) { + $this->logger->debug(sprintf( + '[RetailCRM API Request]: %s URL: "%s", Headers: "%s", Body: "%s"', + $psrRequest->getMethod(), + (string) $psrRequest->getUri(), + json_encode($psrRequest->getHeaders()), + Utils::getBodyContents($psrRequest->getBody()) + )); + } + + try { + $psrResponse = $this->httpClient->sendRequest($psrRequest); + } catch (ClientExceptionInterface | NetworkExceptionInterface $exception) { + $event = new FailureRequestEvent( + $this->baseUrl, + $psrRequest, + null, + new HttpClientException( + sprintf('HTTP client error: %s', $exception->getMessage()), + $exception->getCode(), + $exception + ) + ); + + $this->dispatch($event); + + if (!$event->shouldSuppressThrow()) { + throw $event->getException(); + } + } + + if ( + $this->logger instanceof LoggerInterface && + !($this->logger instanceof NullLogger) && + isset($psrResponse) + ) { + $this->logger->debug(sprintf( + '[RetailCRM API Response]: Status: "%d", Body: "%s"', + $psrResponse->getStatusCode(), + Utils::getBodyContents($psrResponse->getBody()) + )); + } + + if (!isset($psrResponse)) { + return new $type(); + } + + return $this->responseTransformer->createResponse($this->baseUrl, $psrRequest, $psrResponse, $type); + } + + /** + * Returns route with base URI. + * + * @param string $route + * + * @return string + */ + protected function route(string $route): string + { + return sprintf('%s/%s', $this->baseUrl, $route); + } +} diff --git a/src/ResourceGroup/Api.php b/src/ResourceGroup/Api.php new file mode 100644 index 0000000..4e13d31 --- /dev/null +++ b/src/ResourceGroup/Api.php @@ -0,0 +1,163 @@ +api->apiVersions(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Available API versions: ' . implode(', ', $apiVersions->versions); + * ``` + * + * @return \RetailCrm\Api\Model\Response\Api\ApiVersionsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function apiVersions(): ApiVersionsResponse + { + /** @var ApiVersionsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'api-versions', + null, + ApiVersionsResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/credentials" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $credentials = $client->api->credentials(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Available methods ' . implode(', ', $credentials->credentials); + * ``` + * + * @return \RetailCrm\Api\Model\Response\Api\Credentials + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function credentials(): Credentials + { + /** @var Credentials $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'credentials', + null, + Credentials::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Costs.php b/src/ResourceGroup/Costs.php new file mode 100644 index 0000000..00e4c28 --- /dev/null +++ b/src/ResourceGroup/Costs.php @@ -0,0 +1,487 @@ +limit = PaginationLimit::LIMIT_20; + * $costsRequest->page = 1; + * $costsRequest->filter = new CostFilter(); + * $costsRequest->filter->sites = ['moysklad', 'aliexpress']; + * $costsRequest->filter->maxSumm = 20; + * + * try { + * $response = $client->costs->list($costsRequest); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Received costs: ' . print_r($response->costs, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Costs\CostsRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Costs\CostsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function list(?CostsRequest $request = null): CostsResponse + { + /** @var CostsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'costs', + $request, + CostsResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/costs/delete" request. + * + * Example: + * ```php + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Request\Costs\CostsDeleteRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CostsDeleteRequest(); + * $request->ids = [2, 3, 5, 8, 13, 21]; + * + * try { + * $response = $client->costs->costsDelete($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * printf('Deleted %d costs.', $response->count); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Costs\CostsDeleteRequest $request + * + * @return CostsDeleteResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function costsDelete(CostsDeleteRequest $request): CostsDeleteResponse + { + /** @var CostsDeleteResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'costs/delete', + $request, + CostsDeleteResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/costs/upload" request. + * + * Example: + * ```php + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Entity\Costs\Cost; + * use RetailCrm\Api\Model\Request\Costs\CostsUploadRequest; + * use RetailCrm\Api\Model\Entity\Source; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CostsUploadRequest(); + * $cost = new Cost(); + * $cost->sites = ['aliexpress']; + * $cost->source = new Source(); + * $cost->source->source = 'source'; + * $cost->source->campaign = 'campaign'; + * $cost->source->content = 'content'; + * $cost->source->keyword = 'keyword'; + * $cost->source->medium = 'medium'; + * $cost->comment = 'comment'; + * $cost->costItem = 'products-purchase-price'; + * $cost->createdAt = new DateTime(); + * $cost->dateFrom = new DateTime(); + * $cost->dateTo = new DateTime(); + * $cost->summ = 100.10; + * $request->costs = [$cost]; + * + * try { + * $response = $client->costs->costsUpload($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Uploaded costs with IDs: ' . implode(', ', $response->uploadedCosts); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Costs\CostsUploadRequest $request + * + * @return CostsUploadResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function costsUpload(CostsUploadRequest $request): CostsUploadResponse + { + /** @var CostsUploadResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'costs/upload', + $request, + CostsUploadResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/costs/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Entity\Costs\Cost; + * use RetailCrm\Api\Model\Request\Costs\CostsCreateRequest; + * use RetailCrm\Api\Model\Entity\Source; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CostsCreateRequest(); + * $request->site = 'aliexpress'; + * $request->cost = new Cost(); + * $request->cost->sites = ['aliexpress']; + * $request->cost->source = new Source(); + * $request->cost->source->source = 'source'; + * $request->cost->source->campaign = 'campaign'; + * $request->cost->source->content = 'content'; + * $request->cost->source->keyword = 'keyword'; + * $request->cost->source->medium = 'medium'; + * $request->cost->comment = 'comment'; + * $request->cost->costItem = 'products-purchase-price'; + * $request->cost->createdAt = new DateTime(); + * $request->cost->dateFrom = new DateTime(); + * $request->cost->dateTo = new DateTime(); + * $request->cost->summ = 100.10; + * + * try { + * $response = $client->costs->create($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created cost with ID: ' . $response->id; + * ``` + * + * @param \RetailCrm\Api\Model\Request\Costs\CostsCreateRequest $request + * + * @return IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function create(CostsCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'costs/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/costs/{id}" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->costs->get(1); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Received cost: ' . print_r($response->cost, true); + * ``` + * + * @param int $id + * + * @return \RetailCrm\Api\Model\Response\Costs\CostsGetResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function get(int $id): CostsGetResponse + { + /** @var CostsGetResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + sprintf('costs/%d', $id), + null, + CostsGetResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/costs/{id}/delete" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->costs->delete(1); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Status: ' . var_export($response->success, true); + * ``` + * + * @param int $id + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function delete(int $id): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + sprintf('costs/%d/delete', $id), + null, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/costs/{id}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Entity\Costs\Cost; + * use RetailCrm\Api\Model\Request\Costs\CostsEditRequest; + * use RetailCrm\Api\Model\Entity\Source; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CostsEditRequest(); + * $request->site = 'aliexpress'; + * $request->cost = new Cost(); + * $request->cost->sites = ['aliexpress']; + * $request->cost->source = new Source(); + * $request->cost->source->source = 'source'; + * $request->cost->source->campaign = 'campaign'; + * $request->cost->source->content = 'content'; + * $request->cost->source->keyword = 'keyword'; + * $request->cost->source->medium = 'medium'; + * $request->cost->comment = 'comment'; + * $request->cost->costItem = 'products-purchase-price'; + * $request->cost->createdAt = new DateTime(); + * $request->cost->dateFrom = new DateTime(); + * $request->cost->dateTo = new DateTime(); + * $request->cost->summ = 100.10; + * + * try { + * $response = $client->costs->edit(1, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Edited cost with ID: ' . $response->id; + * ``` + * + * @param int $id + * @param \RetailCrm\Api\Model\Request\Costs\CostsEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function edit(int $id, CostsEditRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + sprintf('costs/%d/edit', $id), + $request, + IdResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/CustomFields.php b/src/ResourceGroup/CustomFields.php new file mode 100644 index 0000000..5388bd8 --- /dev/null +++ b/src/ResourceGroup/CustomFields.php @@ -0,0 +1,566 @@ +page = 1; + * $request->limit = PaginationLimit::LIMIT_20; + * $request->filter = new CustomFieldFilter(); + * $request->filter->entity = 'order'; + * $request->filter->viewMode = ['editable']; + * $request->filter->displayArea = ['customer']; + * $request->filter->type = ['string']; + * $request->filter->code = 'bonus'; + * $request->filter->name = 'бонус'; + * + * try { + * $response = $client->customFields->list($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Received fields: ' . print_r($response->customFields, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\CustomFields\CustomFieldsRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\CustomFields\CustomFieldsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function list(?CustomFieldsRequest $request = null): CustomFieldsResponse + { + /** @var CustomFieldsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'custom-fields', + $request, + CustomFieldsResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/custom-fields/dictionaries" request. + * + * Example: + * ```php + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Filter\CustomFields\CustomDictionaryFilter; + * use RetailCrm\Api\Model\Request\CustomFields\CustomFieldsDictionariesRequest; + * use RetailCrm\Api\Enum\PaginationLimit; + + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + + * $request = new CustomFieldsDictionariesRequest(); + * $request->page = 1; + * $request->limit = PaginationLimit::LIMIT_20; + * $request->filter = new CustomDictionaryFilter(); + * $request->filter->code = 'test22'; + * $request->filter->name = 'test22'; + + * try { + * $response = $client->customFields->dictionaries($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + + * echo 'Received dictionaries: ' . print_r($response->customDictionaries, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\CustomFields\CustomFieldsDictionariesRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\CustomFields\CustomFieldsDictionariesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function dictionaries(?CustomFieldsDictionariesRequest $request = null): CustomFieldsDictionariesResponse + { + /** @var CustomFieldsDictionariesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'custom-fields/dictionaries', + $request, + CustomFieldsDictionariesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/custom-fields/dictionaries/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Entity\CustomFields\CustomDictionary; + * use RetailCrm\Api\Model\Entity\CustomFields\SerializedCustomDictionaryElement; + * use RetailCrm\Api\Model\Request\CustomFields\CustomDictionaryCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $dictionary = new CustomDictionary(); + * $element = new SerializedCustomDictionaryElement(); + * $element->name = 'test_1'; + * $element->code = 'test_1'; + * $element->ordering = 10; + * $dictionary->name = 'TestDict'; + * $dictionary->code = 'test_dict'; + * $dictionary->elements = [$element]; + * + * try { + * $response = $client->customFields->dictionariesCreate(new CustomDictionaryCreateRequest($dictionary)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created dictionary ' . $response->code; + * ``` + * + * @param \RetailCrm\Api\Model\Request\CustomFields\CustomDictionaryCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\CustomFields\CustomDictionaryCreateResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function dictionariesCreate(CustomDictionaryCreateRequest $request): CustomDictionaryCreateResponse + { + /** @var CustomDictionaryCreateResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'custom-fields/dictionaries/create', + $request, + CustomDictionaryCreateResponse::class + ); + + return $response; + } + + /** + * Makes GET "/api/v5/custom-fields/dictionaries/{code}" request. + * + * Example: + * ```php + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->customFields->dictionariesGet('test'); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Received dictionary ' . print_r($response->customDictionary, true); + * ``` + * + * @param string $code + * + * @return \RetailCrm\Api\Model\Response\CustomFields\CustomDictionaryGetResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function dictionariesGet(string $code): CustomDictionaryGetResponse + { + /** @var CustomDictionaryGetResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + sprintf('custom-fields/dictionaries/%s', $code), + null, + CustomDictionaryGetResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/custom-fields/dictionaries/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Entity\CustomFields\CustomDictionary; + * use RetailCrm\Api\Model\Entity\CustomFields\SerializedCustomDictionaryElement; + * use RetailCrm\Api\Model\Request\CustomFields\CustomDictionaryCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $dictionary = new CustomDictionary(); + * $element = new SerializedCustomDictionaryElement(); + * $element->name = 'test_1'; + * $element->code = 'test_1'; + * $element->ordering = 10; + * $dictionary->name = 'TestDict'; + * $dictionary->elements = [$element]; + * + * try { + * $response = $client->customFields->dictionariesEdit( + * 'test_dict', + * new CustomDictionaryCreateRequest($dictionary) + * ); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * echo 'Edited dictionary ' . $response->code; + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\CustomFields\CustomDictionaryCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\CustomFields\CustomDictionaryCreateResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function dictionariesEdit( + string $code, + CustomDictionaryCreateRequest $request + ): CustomDictionaryCreateResponse { + /** @var CustomDictionaryCreateResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + sprintf('custom-fields/dictionaries/%s/edit', $code), + $request, + CustomDictionaryCreateResponse::class + ); + + return $response; + } + + /** + * Makes POST "/api/v5/custom-fields/{entity}/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\CustomFields\CustomFieldDisplayArea; + * use RetailCrm\Api\Enum\CustomFields\CustomFieldEntity; + * use RetailCrm\Api\Enum\CustomFields\CustomFieldType; + * use RetailCrm\Api\Enum\CustomFields\CustomFieldViewMode; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Entity\CustomFields\CustomField; + * use RetailCrm\Api\Model\Request\CustomFields\CustomFieldsCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $field = new CustomField(); + * $field->name = 'Description'; + * $field->code = 'description'; + * $field->type = CustomFieldType::STRING; + * $field->ordering = 10; + * $field->displayArea = CustomFieldDisplayArea::CUSTOMER; + * $field->viewMode = CustomFieldViewMode::EDITABLE; + * $field->inFilter = true; + * $field->inList = true; + * $field->inGroupActions = true; + * + * try { + * $response = $client->customFields->create( + * CustomFieldEntity::CUSTOMER, + * new CustomFieldsCreateRequest($field) + * ); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created field ' . print_r($response->code, true); + * ``` + * + * @param string $entity + * @param \RetailCrm\Api\Model\Request\CustomFields\CustomFieldsCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\CustomFields\CustomFieldsCreateResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function create(string $entity, CustomFieldsCreateRequest $request): CustomFieldsCreateResponse + { + /** @var CustomFieldsCreateResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + sprintf('custom-fields/%s/create', $entity), + $request, + CustomFieldsCreateResponse::class + ); + + return $response; + } + + /** + * Makes GET "/api/v5/custom-fields/{entity}/{code}" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\CustomFields\CustomFieldEntity; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->customFields->get(CustomFieldEntity::ORDER, 'item'); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Received field: ' . print_r($response->customField, true); + * ``` + * + * @param string $entity + * @param string $code + * + * @return \RetailCrm\Api\Model\Response\CustomFields\CustomFieldsGetResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function get(string $entity, string $code): CustomFieldsGetResponse + { + /** @var CustomFieldsGetResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + sprintf('custom-fields/%s/%s', $entity, $code), + null, + CustomFieldsGetResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/custom-fields/{entity}/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\CustomFields\CustomFieldDisplayArea; + * use RetailCrm\Api\Enum\CustomFields\CustomFieldEntity; + * use RetailCrm\Api\Enum\CustomFields\CustomFieldType; + * use RetailCrm\Api\Enum\CustomFields\CustomFieldViewMode; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Entity\CustomFields\CustomField; + * use RetailCrm\Api\Model\Request\CustomFields\CustomFieldsCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $field = new CustomField(); + * $field->name = 'Description'; + * $field->type = CustomFieldType::STRING; + * $field->ordering = 10; + * $field->viewMode = CustomFieldViewMode::EDITABLE; + * $field->inFilter = true; + * $field->inList = true; + * $field->inGroupActions = true; + * + * try { + * $response = $client->customFields->edit( + * CustomFieldEntity::CUSTOMER, + * 'description', + * new CustomFieldsCreateRequest($field) + * ); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Edited field ' . print_r($response->code, true); + * ``` + * + * @param string $entity + * @param string $code + * @param \RetailCrm\Api\Model\Request\CustomFields\CustomFieldsCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\CustomFields\CustomFieldsEditResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function edit(string $entity, string $code, CustomFieldsCreateRequest $request): CustomFieldsEditResponse + { + /** @var CustomFieldsEditResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + sprintf('custom-fields/%s/%s/edit', $entity, $code), + $request, + CustomFieldsEditResponse::class + ); + + return $response; + } +} diff --git a/src/ResourceGroup/Customers.php b/src/ResourceGroup/Customers.php new file mode 100644 index 0000000..cfb50de --- /dev/null +++ b/src/ResourceGroup/Customers.php @@ -0,0 +1,785 @@ +limit = PaginationLimit::LIMIT_20; + * $request->page = 1; + * $request->filter = new CustomerFilter(); + * $request->filter->sites = ['moysklad', 'aliexpress']; + * $request->filter->name = '89229112322'; + * $request->filter->isContact = NumericBoolean::TRUE; + * + * try { + * $response = $client->customers->list($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Received customers: ' . print_r($response->customers, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Customers\CustomersRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Customers\CustomersResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function list(?CustomersRequest $request = null): CustomersResponse + { + /** @var CustomersResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'customers', + $request, + CustomersResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers/combine" request. + * + * Example: + * ```php + * use RetailCrm\Api\Model\Entity\Customers\SerializedCustomerReference; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Request\Customers\CustomersCombineRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersCombineRequest(); + * $request->customers = [ + * new SerializedCustomerReference(2), + * new SerializedCustomerReference(3), + * new SerializedCustomerReference(4), + * ]; + * $request->resultCustomer = new SerializedCustomerReference(1); + * + * try { + * $response = $client->customers->combine($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Result: ' . var_export($response->success, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Customers\CustomersCombineRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function combine(CustomersCombineRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers/combine', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\Customers\ContragentType; + * use RetailCrm\Api\Enum\Customers\CustomerType; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Entity\Customers\Customer; + * use RetailCrm\Api\Model\Entity\Customers\CustomerAddress; + * use RetailCrm\Api\Model\Entity\Customers\CustomerContragent; + * use RetailCrm\Api\Model\Entity\Customers\CustomerPhone; + * use RetailCrm\Api\Model\Entity\Customers\CustomerTag; + * use RetailCrm\Api\Model\Request\Customers\CustomersCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $customer = new Customer(); + * $customer->type = CustomerType::CUSTOMER; + * $customer->externalId = 'test_10'; + * $customer->managerId = 24; + * $customer->contragent = new CustomerContragent(); + * $customer->contragent->contragentType = ContragentType::INDIVIDUAL; + * $customer->tags = [ + * new CustomerTag('first'), + * new CustomerTag('second'), + * new CustomerTag('third'), + * ]; + * $customer->customFields = [ + * 'galkatrue' => true + * ]; + * $customer->address = new CustomerAddress(); + * $customer->address->text = '(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado(CO), 81236'; + * $customer->firstName = 'Test'; + * $customer->lastName = 'User'; + * $customer->patronymic = 'Tester'; + * $customer->email = 'tester@example.com'; + * $customer->phones = [ + * new CustomerPhone('(603) 292-6810') + * ]; + * + * $request = new CustomersCreateRequest(); + * $request->site = 'aliexpress'; + * $request->customer = $customer; + * + * try { + * $response = $client->customers->create($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created customer with ID: ' . $response->id; + * ``` + * + * @param \RetailCrm\Api\Model\Request\Customers\CustomersCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function create(CustomersCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers/fix-external-ids" request. + * + * Example: + * ```php + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Entity\Customers\FixExternalRow; + * use RetailCrm\Api\Model\Request\Customers\CustomersFixExternalIdsRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersFixExternalIdsRequest(); + * $request->customers = [ + * new FixExternalRow(1, 'external_id_1'), + * new FixExternalRow(2, 'external_id_2'), + * new FixExternalRow(3, 'external_id_3'), + * ]; + * + * try { + * $response = $client->customers->fixExternalIds($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * ``` + * + * @param \RetailCrm\Api\Model\Request\Customers\CustomersFixExternalIdsRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function fixExternalIds(CustomersFixExternalIdsRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers/fix-external-ids', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/customers/history" request. + * + * Example: + * ```php + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Filter\Customers\CustomerHistoryFilter; + * use RetailCrm\Api\Model\Request\Customers\CustomersHistoryRequest; + * use RetailCrm\Api\Enum\PaginationLimit; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersHistoryRequest(); + * $request->limit = PaginationLimit::LIMIT_20; + * $request->page = 1; + * $request->filter = new CustomerHistoryFilter(); + * $request->filter->sinceId = 2691; + * + * try { + * $response = $client->customers->history($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'History: ' . print_r($response->history, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Customers\CustomersHistoryRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Customers\CustomersHistoryResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function history(?CustomersHistoryRequest $request = null): CustomersHistoryResponse + { + /** @var CustomersHistoryResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'customers/history', + $request, + CustomersHistoryResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/customers/notes" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\Customers\CustomerNoteFilter; + * use RetailCrm\Api\Model\Request\Customers\CustomersNotesRequest; + * use RetailCrm\Api\Enum\PaginationLimit; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersNotesRequest(); + * $request->limit = PaginationLimit::LIMIT_20; + * $request->page = 1; + * $request->filter = new CustomerNoteFilter(); + * $request->filter->customerExternalIds = ['10']; + * $request->filter->createdAtFrom = '2019-08-06 12:00:00'; + * $request->filter->text = 'note'; + * + * try { + * $response = $client->customers->notes($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Notes: ' . print_r($response->notes, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Customers\CustomersNotesRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Customers\CustomerNotesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function notes(?CustomersNotesRequest $request = null): CustomerNotesResponse + { + /** @var CustomerNotesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'customers/notes', + $request, + CustomerNotesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers/notes/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Customers\Customer; + * use RetailCrm\Api\Model\Entity\Customers\CustomerNote; + * use RetailCrm\Api\Model\Request\Customers\CustomersNotesCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersNotesCreateRequest(); + * $request->site = 'moysklad'; + * $request->note = new CustomerNote(); + * $request->note->customer = new Customer(); + * $request->note->customer->externalId = '10'; + * $request->note->managerId = 21; + * $request->note->text = 'Text'; + * + * try { + * $response = $client->customers->notesCreate($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created note with id: ' . $response->id; + * ``` + * + * @param \RetailCrm\Api\Model\Request\Customers\CustomersNotesCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function notesCreate(CustomersNotesCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers/notes/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers/notes/{id}/delete" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->customers->notesDelete(1); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * ``` + * + * @param int $id + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function notesDelete(int $id): SuccessResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + sprintf('customers/notes/%d/delete', $id), + null, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers/upload" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\Customers\ContragentType; + * use RetailCrm\Api\Enum\Customers\CustomerType; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Customers\Customer; + * use RetailCrm\Api\Model\Entity\Customers\CustomerAddress; + * use RetailCrm\Api\Model\Entity\Customers\CustomerContragent; + * use RetailCrm\Api\Model\Entity\Customers\CustomerPhone; + * use RetailCrm\Api\Model\Entity\Customers\CustomerTag; + * use RetailCrm\Api\Model\Entity\Customers\FixExternalRow; + * use RetailCrm\Api\Model\Request\Customers\CustomersUploadRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $customer = new Customer(); + * $customer->type = CustomerType::CUSTOMER; + * $customer->externalId = 'test_10'; + * $customer->managerId = 24; + * $customer->contragent = new CustomerContragent(); + * $customer->contragent->contragentType = ContragentType::INDIVIDUAL; + * $customer->tags = [ + * new CustomerTag('first'), + * new CustomerTag('second'), + * new CustomerTag('third'), + * ]; + * $customer->customFields = [ + * 'galkatrue' => true + * ]; + * $customer->address = new CustomerAddress(); + * $customer->address->text = '(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado(CO), 81236'; + * $customer->firstName = 'Test'; + * $customer->lastName = 'User'; + * $customer->patronymic = 'Tester'; + * $customer->email = 'tester@example.com'; + * $customer->phones = [ + * new CustomerPhone('(603) 292-6810') + * ]; + * + * $request = new CustomersUploadRequest(); + * $request->site = 'aliexpress'; + * $request->customers = [$customer]; + * + * try { + * $response = $client->customers->upload($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Uploaded customers: ' . implode(', ', array_map(static function (FixExternalRow $row) { + * return $row->id; + * }, $response->uploadedCustomers)); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Customers\CustomersUploadRequest $request + * + * @return \RetailCrm\Api\Model\Response\Customers\CustomersUploadResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function upload(CustomersUploadRequest $request): CustomersUploadResponse + { + /** @var CustomersUploadResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers/upload', + $request, + CustomersUploadResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/customers/{externalId}" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Request\BySiteRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->customers->get( + * 4770, + * new BySiteRequest(ByIdentifier::ID, 'bb_demo') + * ); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Customer: ' . print_r($response->customer); + * ``` + * + * @param string|int $identifier + * @param \RetailCrm\Api\Model\Request\BySiteRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Customers\CustomersGetResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function get($identifier, ?BySiteRequest $request = null): CustomersGetResponse + { + + /** @var CustomersGetResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'customers/' . $identifier, + $request, + CustomersGetResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers/{externalId}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Customers\Customer; + * use RetailCrm\Api\Model\Request\Customers\CustomersEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersEditRequest(); + * $request->customer = new Customer(); + * $request->by = ByIdentifier::ID; + * $request->site = 'aliexpress'; + * $request->customer->firstName = 'Test'; + * + * try { + * $response = $client->customers->edit(4770, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Edited customer: ' . $response->id; + * ``` + * + * @param int|string $identifier + * @param \RetailCrm\Api\Model\Request\Customers\CustomersEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\Customers\CustomersEditResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function edit($identifier, CustomersEditRequest $request): CustomersEditResponse + { + /** @var CustomersEditResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers/' . $identifier . '/edit', + $request, + CustomersEditResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/CustomersCorporate.php b/src/ResourceGroup/CustomersCorporate.php new file mode 100644 index 0000000..5c762ab --- /dev/null +++ b/src/ResourceGroup/CustomersCorporate.php @@ -0,0 +1,1428 @@ +filter = new CustomerCorporateFilter(); + * $request->limit = PaginationLimit::LIMIT_20; + * $request->page = 1; + * $request->filter->sites = ['moysklad', 'aliexpress']; + * $request->filter->nickName = ['Test']; + * $request->filter->contragentInn = '5921305044'; + * + * try { + * $response = $client->customersCorporate->list($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Corporate customers: ' . print_r($response->customersCorporate); + * ``` + * + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\CustomersCorporate\CustomersCorporateResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function list(?CustomersCorporateRequest $request = null): CustomersCorporateResponse + { + /** @var CustomersCorporateResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'customers-corporate', + $request, + CustomersCorporateResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers-corporate/combine" request. + * + * Example: + * ```php + * use RetailCrm\Api\Model\Entity\Customers\SerializedCustomerReference; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Request\Customers\CustomersCombineRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersCombineRequest(); + * $request->customers = [ + * new SerializedCustomerReference(2), + * new SerializedCustomerReference(3), + * new SerializedCustomerReference(4), + * ]; + * $request->resultCustomer = new SerializedCustomerReference(1); + * + * try { + * $response = $client->customersCorporate->combine($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Result: ' . var_export($response->success, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Customers\CustomersCombineRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function combine(CustomersCombineRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers-corporate/combine', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers-corporate/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Model\Entity\Source; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Enum\Customers\CustomerType; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Customers\CustomerTag; + * use RetailCrm\Api\Model\Entity\Customers\CustomerAddress; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\Company; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerContact; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\SerializedRelationAbstractCustomer; + * use RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $address = new CustomerAddress(); + * $address->text = '(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado(CO), 81236'; + * + * $contact = new CustomerContact(); + * $contact->customer = new SerializedRelationAbstractCustomer(); + * $contact->customer->externalId = 'test_10'; + * $contact->customer->site = 'aliexpress'; + * + * $company = new Company(); + * $company->name = 'Test Company'; + * $company->brand = 'Test Brand'; + * $company->isMain = true; + * + * $customer = new CustomerCorporate(); + * $customer->source = new Source(); + * $customer->addresses = [$address]; + * $customer->customerContacts = [$contact]; + * $customer->companies = [$company]; + * $customer->source->source = 'chats'; + * $customer->type = CustomerType::CORPORATE_CUSTOMER; + * $customer->externalId = 'test_20'; + * $customer->managerId = 24; + * $customer->nickName = 'Test Corp Company'; + * $customer->tags = [ + * new CustomerTag('first'), + * new CustomerTag('second'), + * new CustomerTag('third'), + * ]; + * $customer->customFields = [ + * 'galkatrue' => true + * ]; + * + * $request = new CustomersCorporateCreateRequest(); + * $request->site = 'aliexpress'; + * $request->customerCorporate = $customer; + * + * try { + * $response = $client->customersCorporate->create($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created new corporate customer with id: ' . $response->id; + * ``` + * + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function create(CustomersCorporateCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers-corporate/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers-corporate/fix-external-ids" request. + * + * Example: + * ```php + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Model\Entity\Customers\FixExternalRow; + * use RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateFixExternalIdsRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersCorporateFixExternalIdsRequest(); + * $request->customersCorporate = [ + * new FixExternalRow(1, 'external_id_1'), + * new FixExternalRow(2, 'external_id_2'), + * new FixExternalRow(3, 'external_id_3'), + * ]; + * + * try { + * $response = $client->customers->fixExternalIds($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * ``` + * + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateFixExternalIdsRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function fixExternalIds(CustomersCorporateFixExternalIdsRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers-corporate/fix-external-ids', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/customers-corporate/history" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\Customers\CustomerHistoryFilter; + * use RetailCrm\Api\Model\Request\Customers\CustomersHistoryRequest; + * use RetailCrm\Api\Enum\PaginationLimit; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersHistoryRequest(); + * $request->limit = PaginationLimit::LIMIT_20; + * $request->page = 1; + * $request->filter = new CustomerHistoryFilter(); + * $request->filter->sinceId = 4241; + * + * try { + * $response = $client->customersCorporate->history($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'History: ' . $response->history; + * ``` + * + * @param \RetailCrm\Api\Model\Request\Customers\CustomersHistoryRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\CustomersCorporate\CustomersCorporateHistoryResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function history(?CustomersHistoryRequest $request = null): CustomersCorporateHistoryResponse + { + /** @var CustomersCorporateHistoryResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'customers-corporate/history', + $request, + CustomersCorporateHistoryResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/customers-corporate/notes" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\Customers\CustomerNoteFilter; + * use RetailCrm\Api\Model\Request\Customers\CustomersNotesRequest; + * use RetailCrm\Api\Enum\PaginationLimit; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersNotesRequest(); + * $request->limit = PaginationLimit::LIMIT_20; + * $request->page = 1; + * $request->filter = new CustomerNoteFilter(); + * $request->filter->customerExternalIds = ['10']; + * $request->filter->createdAtFrom = '2019-08-06 12:00:00'; + * $request->filter->text = 'note'; + * + * try { + * $response = $client->customersCorporate->notes($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Notes: ' . print_r($response->notes, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Customers\CustomersNotesRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Customers\CustomerNotesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function notes(?CustomersNotesRequest $request = null): CustomerNotesResponse + { + /** @var CustomerNotesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'customers-corporate/notes', + $request, + CustomerNotesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers-corporate/notes/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Customers\Customer; + * use RetailCrm\Api\Model\Entity\Customers\CustomerNote; + * use RetailCrm\Api\Model\Request\Customers\CustomersNotesCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersNotesCreateRequest(); + * $request->site = 'moysklad'; + * $request->note = new CustomerNote(); + * $request->note->customer = new Customer(); + * $request->note->customer->externalId = '10'; + * $request->note->managerId = 21; + * $request->note->text = 'Text'; + * + * try { + * $response = $client->customersCorporate->notesCreate($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created note with id: ' . $response->id; + * ``` + * + * @param \RetailCrm\Api\Model\Request\Customers\CustomersNotesCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function notesCreate(CustomersNotesCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers-corporate/notes/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers-corporate/notes/{id}/delete" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->customersCorporate->notesDelete(1); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * ``` + * + * @param int $id + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function notesDelete(int $id): SuccessResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + sprintf('customers-corporate/notes/%d/delete', $id), + null, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers-corporate/upload" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\Customers\CustomerType; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Customers\CustomerAddress; + * use RetailCrm\Api\Model\Entity\Customers\CustomerTag; + * use RetailCrm\Api\Model\Entity\Customers\FixExternalRow; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\Company; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerContact; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\SerializedRelationAbstractCustomer; + * use RetailCrm\Api\Model\Entity\Source; + * use RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateUploadRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $address = new CustomerAddress(); + * $address->text = '(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado(CO), 81236'; + * + * $contact = new CustomerContact(); + * $contact->customer = new SerializedRelationAbstractCustomer(); + * $contact->customer->externalId = 'test_10'; + * $contact->customer->site = 'aliexpress'; + * + * $company = new Company(); + * $company->name = 'Test Company'; + * $company->brand = 'Test Brand'; + * $company->isMain = true; + * + * $customer = new CustomerCorporate(); + * $customer->source = new Source(); + * $customer->addresses = [$address]; + * $customer->customerContacts = [$contact]; + * $customer->companies = [$company]; + * $customer->source->source = 'chats'; + * $customer->type = CustomerType::CORPORATE_CUSTOMER; + * $customer->externalId = 'test_20'; + * $customer->managerId = 24; + * $customer->nickName = 'Test Corp Company'; + * $customer->tags = [ + * new CustomerTag('first'), + * new CustomerTag('second'), + * new CustomerTag('third'), + * ]; + * $customer->customFields = [ + * 'galkatrue' => true + * ]; + * + * $request = new CustomersCorporateUploadRequest(); + * $request->site = 'aliexpress'; + * $request->customersCorporate = [$customer]; + * + * try { + * $response = $client->customersCorporate->upload($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Uploaded customers: ' . implode(', ', array_map(static function (FixExternalRow $row) { + * return $row->id; + * }, $response->uploadedCustomers)); + * ``` + * + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateUploadRequest $request + * + * @return \RetailCrm\Api\Model\Response\Customers\CustomersUploadResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function upload(CustomersCorporateUploadRequest $request): CustomersUploadResponse + { + /** @var CustomersUploadResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers-corporate/upload', + $request, + CustomersUploadResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/customers-corporate/{externalId}" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Request\BySiteRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersGetRequest(); + * $request->site = 'bb_demo'; + * $request->by = ByIdentifier::ID; + * + * try { + * $response = $client->customersCorporate->get( + * 4770, + * new BySiteRequest(ByIdentifier::ID, 'bb_demo') + * ); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Corporate customer: ' . print_r($response->customerCorporate); + * ``` + * + * @param string|int $identifier + * @param \RetailCrm\Api\Model\Request\BySiteRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\CustomersCorporate\CustomersCorporateGetResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function get($identifier, ?BySiteRequest $request = null): CustomersCorporateGetResponse + { + /** @var CustomersCorporateGetResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'customers-corporate/' . $identifier, + $request, + CustomersCorporateGetResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/customers-corporate/{externalId}/addresses" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\CustomersCorporate\CustomerAddressFilter; + * use RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateAddressesRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersCorporateAddressesRequest(); + * $request->filter = new CustomerAddressFilter(); + * $request->site = 'aliexpress'; + * $request->by = ByIdentifier::ID; + * $request->filter->ids = [1]; + * + * try { + * $response = $client->customersCorporate->addresses(1, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Addresses: ' . print_r($response->addresses, true); + * ``` + * + * @param string|int $identifier + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateAddressesRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\CustomersCorporate\CustomersCorporateAddressesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function addresses( + $identifier, + ?CustomersCorporateAddressesRequest $request = null + ): CustomersCorporateAddressesResponse { + /** @var CustomersCorporateAddressesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'customers-corporate/' . $identifier . '/addresses', + $request, + CustomersCorporateAddressesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers-corporate/{externalId}/addresses/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Customers\CustomerAddress; + * use RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateAddressesCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersCorporateAddressesCreateRequest(); + * $request->address = new CustomerAddress(); + * $request->site = 'aliexpress'; + * $request->by = ByIdentifier::ID; + * $request->address->text = '(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado(CO), 81236'; + * + * try { + * $response = $client->customersCorporate->addressesCreate(1, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Address ID: ' . $response->id; + * ``` + * + * @param int|string $identifier + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateAddressesCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function addressesCreate($identifier, CustomersCorporateAddressesCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers-corporate/' . $identifier . '/addresses/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers-corporate/{externalId}/addresses/{entityExternalId}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Customers\CustomerAddress; + * use RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateAddressesEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersCorporateAddressesEditRequest(); + * $request->address = new CustomerAddress(); + * $request->site = 'aliexpress'; + * $request->by = ByIdentifier::ID; + * $request->entityBy = ByIdentifier::ID; + * $request->address->text = '(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado(CO), 81236'; + * + * try { + * $response = $client->customersCorporate->addressesEdit(1, 1, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Edited address ID: ' . $response->id; + * ``` + * + * @param int|string $customerId + * @param int|string $addressId + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateAddressesEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function addressesEdit($customerId, $addressId, CustomersCorporateAddressesEditRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers-corporate/' . $customerId . '/addresses/' . $addressId . '/edit', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/customers-corporate/{externalId}/companies" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\CustomersCorporate\CompanyFilter; + * use RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateCompaniesRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersCorporateCompaniesRequest(); + * $request->filter = new CompanyFilter(); + * $request->site = 'aliexpress'; + * $request->by = ByIdentifier::ID; + * $request->filter->ids = [772]; + * + * try { + * $response = $client->customersCorporate->companies(1, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Companies: ' . print_r($response->companies, true); + * ``` + * + * @param string|int $identifier + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateCompaniesRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\CustomersCorporate\CustomersCorporateCompaniesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function companies( + $identifier, + ?CustomersCorporateCompaniesRequest $request = null + ): CustomersCorporateCompaniesResponse { + /** @var CustomersCorporateCompaniesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'customers-corporate/' . $identifier . '/companies', + $request, + CustomersCorporateCompaniesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers-corporate/{externalId}/companies/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Customers\CustomerAddress; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\Company; + * use RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateCompaniesCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersCorporateCompaniesCreateRequest(); + * $request->company = new Company(); + * $request->company->address = new CustomerAddress(); + * $request->site = 'aliexpress'; + * $request->by = ByIdentifier::ID; + * $request->company->name = 'Test Company'; + * $request->company->brand = 'Test Brand'; + * $request->company->address->id = 1; + * $request->company->isMain = true; + * + * try { + * $response = $client->customersCorporate->companiesCreate(1, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created company ID: ' . $response->id; + * ``` + * + * @param int|string $identifier + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateCompaniesCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function companiesCreate($identifier, CustomersCorporateCompaniesCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers-corporate/' . $identifier . '/companies/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers-corporate/{externalId}/companies/{entityExternalId}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Customers\CustomerAddress; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\Company; + * use RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateCompaniesEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersCorporateCompaniesEditRequest(); + * $request->company = new Company(); + * $request->company->address = new CustomerAddress(); + * $request->site = 'aliexpress'; + * $request->by = ByIdentifier::ID; + * $request->entityBy = ByIdentifier::ID; + * $request->company->name = 'Test Company'; + * $request->company->brand = 'Test Brand'; + * $request->company->address->id = 3559; + * $request->company->isMain = true; + * + * try { + * $response = $client->customersCorporate->companiesEdit(1, 1, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Edited company ID: ' . $response->id; + * ``` + * + * @param int|string $customerId + * @param int|string $companyId + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateCompaniesEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function companiesEdit($customerId, $companyId, CustomersCorporateCompaniesEditRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers-corporate/' . $customerId . '/companies/' . $companyId . '/edit', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/customers-corporate/{externalId}/contacts" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\CustomersCorporate\CustomerContactFilter; + * use RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateContactsRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersCorporateContactsRequest(); + * $request->filter = new CustomerContactFilter(); + * $request->site = 'aliexpress'; + * $request->by = ByIdentifier::ID; + * $request->filter->contactIds = [5039]; + * $request->filter->contactExternalIds = ['test_10']; + * + * try { + * $response = $client->customersCorporate->contacts(1, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Contacts: ' . print_r($response->contacts, true); + * ``` + * + * @param string|int $identifier + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateContactsRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\CustomersCorporate\CustomersCorporateContactsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function contacts( + $identifier, + ?CustomersCorporateContactsRequest $request = null + ): CustomersCorporateContactsResponse { + /** @var CustomersCorporateContactsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'customers-corporate/' . $identifier . '/contacts', + $request, + CustomersCorporateContactsResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers-corporate/{externalId}/contacts/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerContact; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerContactCompany; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\SerializedRelationAbstractCustomer; + * use RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateContactsCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $company = new CustomerContactCompany(); + * $company->id = 776; + * + * $request = new CustomersCorporateContactsCreateRequest(); + * $request->contact = new CustomerContact(); + * $request->contact->customer = new SerializedRelationAbstractCustomer(); + * $request->contact->customer->id = 4985; + * $request->site = 'aliexpress'; + * $request->by = ByIdentifier::ID; + * $request->contact->companies = [$company]; + * + * try { + * $response = $client->customersCorporate->contactsCreate(1, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created contact: ' . $response->id; + * ``` + * + * @param int|string $identifier + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateContactsCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function contactsCreate($identifier, CustomersCorporateContactsCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers-corporate/' . $identifier . '/contacts/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers-corporate/{externalId}/contacts/{entityExternalId}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerContact; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerContactCompany; + * use RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateContactsEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $company = new CustomerContactCompany(); + * $company->id = 776; + * + * $request = new CustomersCorporateContactsEditRequest(); + * $request->contact = new CustomerContact(); + * $request->site = 'aliexpress'; + * $request->by = ByIdentifier::ID; + * $request->entityBy = ByIdentifier::ID; + * $request->contact->companies = [$company]; + * + * try { + * $response = $client->customersCorporate->contactsEdit(1, 1, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Edited contact: ' . $response->id; + * ``` + * + * @param int|string $customerId + * @param int|string $contactId + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateContactsEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function contactsEdit($customerId, $contactId, CustomersCorporateContactsEditRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers-corporate/' . $customerId . '/contacts/' . $contactId . '/edit', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/customers-corporate/{externalId}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate; + * use RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new CustomersCorporateEditRequest(); + * $request->customerCorporate = new CustomerCorporate(); + * $request->customerCorporate->nickName = 'Test Edited Customer'; + * $request->site = 'aliexpress'; + * $request->by = ByIdentifier::ID; + * + * try { + * $response = $client->customersCorporate->edit(1, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Edited corporate customer with ID: ' . $response->id; + * ``` + * + * @param int|string $identifier + * @param \RetailCrm\Api\Model\Request\CustomersCorporate\CustomersCorporateEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\Customers\CustomersEditResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function edit($identifier, CustomersCorporateEditRequest $request): CustomersEditResponse + { + /** @var CustomersEditResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'customers-corporate/' . $identifier . '/edit', + $request, + CustomersEditResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Delivery.php b/src/ResourceGroup/Delivery.php new file mode 100644 index 0000000..755abe6 --- /dev/null +++ b/src/ResourceGroup/Delivery.php @@ -0,0 +1,454 @@ +address = new OrderDeliveryAddress(); + * $delivery->date = (new DateTime())->add(new DateInterval('P1D')); + * $delivery->time = TimeInterval::withCustomInterval('from 9:00 am to 18:00 pm'); + * $delivery->address->index = '12010'; + * $delivery->address->building = '9850'; + * $delivery->address->countryIso = CountryCodeIso3166::UNITED_STATES_OF_AMERICA; + * $delivery->address->city = 'New York'; + * $delivery->address->street = 'Griffin Ave.'; + * + * $item = new SerializedOrderProduct(); + * $item->initialPrice = 1000.0; + * $item->discountManualPercent = 5.0; + * $item->quantity = 10; + * + * $order = new SerializedOrder(); + * $order->delivery = $delivery; + * $order->items = [$item]; + * $order->height = 100; + * $order->width = 100; + * $order->weight = 100; + * $order->length = 100; + * + * $request = new DeliveryCalculateRequest(); + * $request->order = $order; + * $request->deliveryTypeCodes = ['2', '3', '8', '9', '10', '11']; + * + * try { + * $response = $client->delivery->calculate($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Calculate result: ' . print_r($response->calculations, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Delivery\DeliveryCalculateRequest $request + * + * @return \RetailCrm\Api\Model\Response\Delivery\DeliveryCalculateResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function calculate(DeliveryCalculateRequest $request): DeliveryCalculateResponse + { + /** @var DeliveryCalculateResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'delivery/calculate', + $request, + DeliveryCalculateResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/delivery/generic/{subcode}/tracking" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Delivery\RequestStatusUpdateItem; + * use RetailCrm\Api\Model\Entity\Delivery\StatusInfo; + * use RetailCrm\Api\Model\Request\Delivery\TrackingRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $status = new StatusInfo(); + * $status->code = 'code'; + * $status->comment = 'comment'; + * $status->updatedAt = new DateTime(); + * + * $item = new RequestStatusUpdateItem(); + * $item->deliveryId = 'boxberry'; + * $item->trackNumber = 'track'; + * $item->cost = 100; + * $item->history = [$status]; + * + * $request = new TrackingRequest(); + * $request->statusUpdate = [$item]; + * + * try { + * $response = $client->delivery->tracking('boxberry-1-5f8064212c612', $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * ``` + * + * @param string $subcode + * @param \RetailCrm\Api\Model\Request\Delivery\TrackingRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function tracking(string $subcode, TrackingRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'delivery/generic/' . $subcode . '/tracking', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/delivery/shipments" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\Delivery\ApiDeliveryShipmentFilterType; + * use RetailCrm\Api\Model\Request\Delivery\DeliveryShipmentsRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new DeliveryShipmentsRequest(); + * $request->filter = new ApiDeliveryShipmentFilterType(); + * $request->filter->dateFrom = '2020-01-15'; + * $request->filter->orderNumber = '6911C'; + * + * try { + * $response = $client->delivery->shipments($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Shipments: ' . print_r($response->deliveryShipments, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Delivery\DeliveryShipmentsRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Delivery\DeliveryShipmentsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function shipments(?DeliveryShipmentsRequest $request = null): DeliveryShipmentsResponse + { + /** @var DeliveryShipmentsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'delivery/shipments', + $request, + DeliveryShipmentsResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/delivery/shipments/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Delivery\DeliveryShipment; + * use RetailCrm\Api\Model\Entity\Delivery\SerializedEntityOrder; + * use RetailCrm\Api\Model\Entity\Delivery\TimeInterval; + * use RetailCrm\Api\Model\Request\Delivery\DeliveryShipmentsCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $shipment = new DeliveryShipment(); + * $shipment->integrationCode = 'boxberry-249'; + * $shipment->externalId = 'test_30'; + * $shipment->managerId = 19; + * $shipment->store = 'main1'; + * $shipment->date = new DateTime(); + * $shipment->time = TimeInterval::withTextInterval('18:00', '22:00'); + * $shipment->orders = [ + * SerializedEntityOrder::withNumber('8124705923428910') + * ]; + * + * $request = new DeliveryShipmentsCreateRequest(); + * $request->site = 'aliexpress'; + * $request->deliveryType = 'boxberry'; + * $request->deliveryShipment = $shipment; + * + * try { + * $response = $client->delivery->shipmentsCreate($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * printf('Created shipment %d with status "%s"', $response->id, $response->status); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Delivery\DeliveryShipmentsCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\Delivery\DeliveryShipmentsCreateResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function shipmentsCreate(DeliveryShipmentsCreateRequest $request): DeliveryShipmentsCreateResponse + { + /** @var DeliveryShipmentsCreateResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'delivery/shipments/create', + $request, + DeliveryShipmentsCreateResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/delivery/shipments/{id}" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->delivery->shipmentsGet('1'); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Shipment №1: ' . print_r($response->deliveryShipment, true); + * ``` + * + * @param string $id + * + * @return \RetailCrm\Api\Model\Response\Delivery\DeliveryShipmentsGetResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function shipmentsGet(string $id): DeliveryShipmentsGetResponse + { + /** @var DeliveryShipmentsGetResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'delivery/shipments/' . $id, + null, + DeliveryShipmentsGetResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/delivery/shipments/{id}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Delivery\DeliveryShipment; + * use RetailCrm\Api\Model\Entity\Delivery\SerializedEntityOrder; + * use RetailCrm\Api\Model\Entity\Delivery\TimeInterval; + * use RetailCrm\Api\Model\Request\Delivery\DeliveryShipmentsCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $shipment = new DeliveryShipment(); + * $shipment->integrationCode = 'boxberry-249'; + * $shipment->externalId = 'test_30'; + * $shipment->managerId = 19; + * $shipment->store = 'main1'; + * $shipment->date = new DateTime(); + * $shipment->time = TimeInterval::withTextInterval('18:00', '22:00'); + * $shipment->orders = [ + * SerializedEntityOrder::withNumber('8124705923428910') + * ]; + * + * $request = new DeliveryShipmentsCreateRequest(); + * $request->site = 'aliexpress'; + * $request->deliveryType = 'boxberry'; + * $request->deliveryShipment = $shipment; + * + * try { + * $response = $client->delivery->shipmentsEdit('1', $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * printf('Edited shipment %d with status "%s"', $response->id, $response->status); + * ``` + * + * @param string $id + * @param \RetailCrm\Api\Model\Request\Delivery\DeliveryShipmentsCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\Delivery\DeliveryShipmentsCreateResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function shipmentsEdit(string $id, DeliveryShipmentsCreateRequest $request): DeliveryShipmentsCreateResponse + { + /** @var DeliveryShipmentsCreateResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'delivery/shipments/' . $id . '/edit', + $request, + DeliveryShipmentsCreateResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Files.php b/src/ResourceGroup/Files.php new file mode 100644 index 0000000..c1f72dd --- /dev/null +++ b/src/ResourceGroup/Files.php @@ -0,0 +1,373 @@ +filter = new FileFilter(); + * $request->filter->sites = ['moysklad', 'aliexpress']; + * $request->filter->sizeTo = 20; + * $request->page = 1; + * $request->limit = PaginationLimit::LIMIT_20; + * + * try { + * $response = $client->files->list($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Files: ' . print_r($response->files, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Files\FilesRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Files\FilesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function list(?FilesRequest $request = null): FilesResponse + { + /** @var FilesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'files', + $request, + FilesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/files/upload" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Request\Files\FilesUploadRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * $fileData = $client->getStreamFactory()->createStreamFromFile('report.xlsx'); + * + * try { + * $response = $client->files->upload(new FilesUploadRequest($fileData)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Uploaded file: ' . print_r($response->file->id, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Files\FilesUploadRequest $request + * + * @return \RetailCrm\Api\Model\Response\Files\FilesUploadResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function upload(FilesUploadRequest $request): FilesUploadResponse + { + /** @var FilesUploadResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'files/upload', + $request, + FilesUploadResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/files/{id}" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->files->get(1); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Got file: ' . print_r($response->file, true); + * ``` + * + * @param int $fileId + * + * @return \RetailCrm\Api\Model\Response\Files\FilesGetResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function get(int $fileId): FilesGetResponse + { + /** @var FilesGetResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'files/' . $fileId, + null, + FilesGetResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/files/{id}/delete" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->files->delete(1); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * ``` + * + * @param int $fileId + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function delete(int $fileId): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'files/' . $fileId . '/delete', + null, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/files/{id}/download" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->files->download(1); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * printf('Saving downloaded file to "%s."', $response->fileName); + * file_put_contents($response->fileName, $response->data->getContents()); + * ``` + * + * @param int $fileId + * + * @return \RetailCrm\Api\Model\Response\Files\FilesDownloadResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function download(int $fileId): FilesDownloadResponse + { + /** @var FilesDownloadResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'files/' . $fileId . '/download', + null, + '' + ); + return $response; + } + + /** + * Makes POST "/api/v5/files/{id}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Files\File; + * use RetailCrm\Api\Model\Request\Files\FilesEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new FilesEditRequest(); + * $request->file = new File(); + * $request->file->filename = 'Test File.xml'; + * + * try { + * $response = $client->files->edit(1, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Edited file: ' . $response->file->id; + * ``` + * + * @param int $fileId + * @param \RetailCrm\Api\Model\Request\Files\FilesEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\Files\FilesUploadResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function edit(int $fileId, FilesEditRequest $request): FilesUploadResponse + { + /** @var FilesUploadResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'files/' . $fileId . '/edit', + $request, + FilesUploadResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Integration.php b/src/ResourceGroup/Integration.php new file mode 100644 index 0000000..0195688 --- /dev/null +++ b/src/ResourceGroup/Integration.php @@ -0,0 +1,151 @@ +integration->get('mg-fbmessenger'); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'FBMessenger Module: ' . print_r($response->integrationModule, true); + * ``` + * + * @param string $code + * + * @return \RetailCrm\Api\Model\Response\Integration\IntegrationModulesGetResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function get(string $code): IntegrationModulesGetResponse + { + /** @var IntegrationModulesGetResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'integration-modules/' . $code, + null, + IntegrationModulesGetResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/integration-modules/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Integration\IntegrationModule; + * use RetailCrm\Api\Model\Entity\Integration\Integrations; + * use RetailCrm\Api\Model\Entity\Integration\Transport\TransportConfiguration; + * use RetailCrm\Api\Model\Request\Integration\IntegrationModulesEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $module = new IntegrationModule(); + * $module->integrations = new Integrations(); + * $module->integrations->mgTransport = new TransportConfiguration(); + * + * $module->clientId = 'e029f3dd545147c6428d12d9524f33b806e9310947430773c6c82719e4c41904'; + * $module->code = 'mg-fbmessenger'; + * $module->integrationCode = 'mg-fbmessenger'; + * $module->name = 'Facebook Messenger'; + * $module->logo = 'https://mg-tp-fbm-s1.retailcrm.pro/static/fbmessenger_logo.svg'; + * $module->baseUrl = 'https://mg-tp-fbm-s1.retailcrm.pro/'; + * $module->actions = ['activity' => '/actions/activity']; + * $module->accountUrl = 'https://mg-tp-fbm-s1.retailcrm.pro/settings/clientId'; + * $module->integrations->mgTransport->webhookUrl = 'https://mg-tp-fbm-s1.retailcrm.pro/webhook/'; + * + * try { + * $response = $client->integration->edit('mg-fbmessenger', new IntegrationModulesEditRequest($module)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Info: ' . print_r($response->info, true); + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\Integration\IntegrationModulesEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\Integration\IntegrationModulesEditResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function edit(string $code, IntegrationModulesEditRequest $request): IntegrationModulesEditResponse + { + /** @var IntegrationModulesEditResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'integration-modules/' . $code . '/edit', + $request, + IntegrationModulesEditResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Loyalty.php b/src/ResourceGroup/Loyalty.php new file mode 100644 index 0000000..b5e5aa5 --- /dev/null +++ b/src/ResourceGroup/Loyalty.php @@ -0,0 +1,549 @@ +customer = new SerializedEntityCustomer(); + * + * $account->customer->id = 4787; + * $account->cardNumber = '2222 3333 4444 5555'; + * $account->phoneNumber = '88005553125'; + * + * $request->site = 'bitrix-test'; + * $request->loyaltyAccount = $account; + * + * try { + * $response = $client->loyalty->accountCreate($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created loyalty account: ' . print_r($response->loyaltyAccount, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Loyalty\LoyaltyAccountCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\Loyalty\LoyaltyAccountCreateResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function accountCreate(LoyaltyAccountCreateRequest $request): LoyaltyAccountCreateResponse + { + /** @var LoyaltyAccountCreateResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'loyalty/account/create', + $request, + LoyaltyAccountCreateResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/loyalty/account/{id}/activate" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->loyalty->accountActivate(159); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Activated loyalty account: ' . print_r($response->loyaltyAccount, true); + * ``` + * + * @param int $id + * + * @return \RetailCrm\Api\Model\Response\Loyalty\LoyaltyAccountActivateResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function accountActivate(int $id): LoyaltyAccountActivateResponse + { + /** @var LoyaltyAccountActivateResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'loyalty/account/' . $id . '/activate', + null, + LoyaltyAccountActivateResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/loyalty/account/{id}/bonus/credit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Request\Loyalty\LoyaltyBonusCreditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new LoyaltyBonusCreditRequest(); + * $request->amount = 100; + * $request->activationDate = new DateTime(); + * $request->expireDate = (new DateTime())->add(new DateInterval('P14D')); + * $request->comment = 'Monthly membership bonuses.'; + * + * try { + * $response = $client->loyalty->accountBonusCredit(159, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Credited bonuses for account: ' . print_r($response->loyaltyBonus, true); + * ``` + * + * @param int $id + * @param \RetailCrm\Api\Model\Request\Loyalty\LoyaltyBonusCreditRequest $request + * + * @return \RetailCrm\Api\Model\Response\Loyalty\LoyaltyBonusCreditResponse + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + */ + public function accountBonusCredit(int $id, LoyaltyBonusCreditRequest $request): LoyaltyBonusCreditResponse + { + /** @var LoyaltyBonusCreditResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'loyalty/account/' . $id . '/bonus/credit', + $request, + LoyaltyBonusCreditResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/loyalty/account/{id}/bonus/operations" request. + * + * Example: + * ```php + * use RetailCrm\Api\Component\Transformer\DateTimeTransformer; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\Loyalty\LoyaltyAccountBonusOperationsApiFilterType; + * use RetailCrm\Api\Model\Request\Loyalty\LoyaltyBonusOperationsRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new LoyaltyBonusOperationsRequest(); + * $request->filter = new LoyaltyAccountBonusOperationsApiFilterType(); + * $request->filter->createdAtFrom = DateTimeTransformer::create('2020-01-01 00:00:00'); + * $request->filter->createdAtTo = DateTimeTransformer::create('2021-08-01 00:00:00'); + * + * try { + * $response = $client->loyalty->accountBonusOperations(159, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Account operations: ' . print_r($response->bonusOperations, true); + * ``` + * + * @param int $id + * @param \RetailCrm\Api\Model\Request\Loyalty\LoyaltyBonusOperationsRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Loyalty\LoyaltyBonusOperationsResponse + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + */ + public function accountBonusOperations( + int $id, + ?LoyaltyBonusOperationsRequest $request = null + ): LoyaltyBonusOperationsResponse { + /** @var LoyaltyBonusOperationsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'loyalty/account/' . $id . '/bonus/operations', + $request, + LoyaltyBonusOperationsResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/loyalty/account/{id}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Loyalty\LoyaltyAccount; + * use RetailCrm\Api\Model\Request\Loyalty\LoyaltyAccountEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $account = new LoyaltyAccount(); + * $account->cardNumber = '4444 5555 6666 7777'; + * $account->phoneNumber = '88005553000'; + * + * try { + * $response = $client->loyalty->accountEdit(159, new LoyaltyAccountEditRequest($account)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Response: ' . print_r($response, true); + * ``` + * + * @param int $id + * @param \RetailCrm\Api\Model\Request\Loyalty\LoyaltyAccountEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\Loyalty\LoyaltyAccountCreateResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function accountEdit(int $id, LoyaltyAccountEditRequest $request): LoyaltyAccountCreateResponse + { + /** @var LoyaltyAccountCreateResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'loyalty/account/' . $id . '/edit', + $request, + LoyaltyAccountCreateResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/loyalty/accounts" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\Loyalty\AccountStatus; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\Loyalty\LoyaltyAccountApiFilterType; + * use RetailCrm\Api\Model\Request\Loyalty\LoyaltyAccountsRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new LoyaltyAccountsRequest(); + * $request->filter = new LoyaltyAccountApiFilterType(); + * $request->filter->status = AccountStatus::ACTIVATED; + * + * try { + * $response = $client->loyalty->accounts($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Accounts: ' . print_r($response->loyaltyAccounts, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Loyalty\LoyaltyAccountsRequest $request + * + * @return \RetailCrm\Api\Model\Response\Loyalty\LoyaltyAccountsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function accounts(LoyaltyAccountsRequest $request): LoyaltyAccountsResponse + { + /** @var LoyaltyAccountsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'loyalty/accounts', + $request, + LoyaltyAccountsResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/loyalty/calculate" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\Loyalty\PrivilegeType; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\CustomersCorporate\SerializedRelationAbstractCustomer; + * use RetailCrm\Api\Model\Entity\Loyalty\SerializedOrder; + * use RetailCrm\Api\Model\Entity\Loyalty\SerializedOrderDelivery; + * use RetailCrm\Api\Model\Entity\Loyalty\SerializedOrderProduct; + * use RetailCrm\Api\Model\Entity\Loyalty\SerializedOrderProductOffer; + * use RetailCrm\Api\Model\Request\Loyalty\LoyaltyCalculateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $item = new SerializedOrderProduct(); + * $item->offer = SerializedOrderProductOffer::withId(1); + * $item->quantity = 10; + * + * $order = new SerializedOrder(); + * $order->customer = SerializedRelationAbstractCustomer::withExternalId( + * '47876750', + * 'bitrix-test' + * ); + * $order->items = [$item]; + * $order->delivery = new SerializedOrderDelivery(100); + * $order->privilegeType = PrivilegeType::NONE; + * + * $request = new LoyaltyCalculateRequest(); + * $request->site = 'bitrix-test'; + * $request->bonuses = 5; + * $request->order = $order; + * + * try { + * $response = $client->loyalty->calculate($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Response: ' . print_r($response, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Loyalty\LoyaltyCalculateRequest $request + * + * @return \RetailCrm\Api\Model\Response\Loyalty\LoyaltyCalculateResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function calculate(LoyaltyCalculateRequest $request): LoyaltyCalculateResponse + { + /** @var LoyaltyCalculateResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'loyalty/calculate', + $request, + LoyaltyCalculateResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/loyalty/loyalties" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\NumericBoolean; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\Loyalty\LoyaltyApiFilterType; + * use RetailCrm\Api\Model\Request\Loyalty\LoyaltiesRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new LoyaltiesRequest(); + * $request->filter = new LoyaltyApiFilterType(); + * $request->filter->active = NumericBoolean::TRUE; + * $request->filter->blocked = NumericBoolean::FALSE; + * + * try { + * $response = $client->loyalty->loyalties($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Loyalties: ' . print_r($response->loyalties, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Loyalty\LoyaltiesRequest $request + * + * @return \RetailCrm\Api\Model\Response\Loyalty\LoyaltiesResponse + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + */ + public function loyalties(LoyaltiesRequest $request): LoyaltiesResponse + { + /** @var LoyaltiesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'loyalty/loyalties', + $request, + LoyaltiesResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Orders.php b/src/ResourceGroup/Orders.php new file mode 100644 index 0000000..5f760ed --- /dev/null +++ b/src/ResourceGroup/Orders.php @@ -0,0 +1,1047 @@ +filter = new OrderFilter(); + * $request->filter->ids = [7141]; + * + * try { + * $response = $client->orders->list($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Got orders: ' . print_r($response->orders, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Orders\OrdersRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Orders\OrdersResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function list(?OrdersRequest $request = null): OrdersResponse + { + /** @var OrdersResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'orders', + $request, + OrdersResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/orders/combine" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\CombineTechnique; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Orders\SerializedOrderReference; + * use RetailCrm\Api\Model\Request\Orders\OrdersCombineRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new OrdersCombineRequest(); + * $request->order = new SerializedOrderReference(7143); + * $request->resultOrder = new SerializedOrderReference(7140); + * $request->technique = CombineTechnique::SUMM; + * + * try { + * $response = $client->orders->combine($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Combine errors: ' . print_r($response->errors, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Orders\OrdersCombineRequest $request + * + * @return \RetailCrm\Api\Model\Response\Orders\OrdersCombineResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function combine(OrdersCombineRequest $request): OrdersCombineResponse + { + /** @var OrdersCombineResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'orders/combine', + $request, + OrdersCombineResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/orders/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\CountryCodeIso3166; + * use RetailCrm\Api\Enum\Customers\CustomerType; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Orders\Delivery\OrderDeliveryAddress; + * use RetailCrm\Api\Model\Entity\Orders\Delivery\SerializedOrderDelivery; + * use RetailCrm\Api\Model\Entity\Orders\Items\Offer; + * use RetailCrm\Api\Model\Entity\Orders\Items\OrderProduct; + * use RetailCrm\Api\Model\Entity\Orders\Items\PriceType; + * use RetailCrm\Api\Model\Entity\Orders\Items\Unit; + * use RetailCrm\Api\Model\Entity\Orders\Order; + * use RetailCrm\Api\Model\Entity\Orders\Payment; + * use RetailCrm\Api\Model\Entity\Orders\SerializedRelationCustomer; + * use RetailCrm\Api\Model\Request\Orders\OrdersCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new OrdersCreateRequest(); + * $order = new Order(); + * $payment = new Payment(); + * $delivery = new SerializedOrderDelivery(); + * $deliveryAddress = new OrderDeliveryAddress(); + * $offer = new Offer(); + * $item = new OrderProduct(); + * + * $payment->type = 'bank-card'; + * $payment->status = 'paid'; + * $payment->amount = 1000; + * $payment->paidAt = new DateTime(); + * + * $deliveryAddress->index = '344001'; + * $deliveryAddress->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; + * $deliveryAddress->region = 'Ростовская область'; + * $deliveryAddress->city = 'г. Ростов-на-Дону'; + * $deliveryAddress->street = 'ул. Пушкинская'; + * $deliveryAddress->building = '10'; + * + * $delivery->address = $deliveryAddress; + * $delivery->cost = 0; + * $delivery->netCost = 0; + * + * $offer->name = 'Сборка №1445123'; + * $offer->displayName = 'Сборка №1445123'; + * $offer->xmlId = 'tGunLo27jlPGmbA8BrHxY2'; + * $offer->article = '14451445-14451445'; + * $offer->unit = new Unit('796', 'Штука', 'шт'); + * + * $item->offer = $offer; + * $item->priceType = new PriceType('base'); + * $item->quantity = 1; + * $item->purchasePrice = 60; + * + * $order->delivery = $delivery; + * $order->items = [$item]; + * $order->payments = [$payment]; + * $order->orderType = 'test'; + * $order->orderMethod = 'phone'; + * $order->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; + * $order->firstName = 'Test'; + * $order->lastName = 'User'; + * $order->patronymic = 'Patronymic'; + * $order->phone = '89003005069'; + * $order->email = 'testuser12345678901@example.com'; + * $order->managerId = 28; + * $order->customer = SerializedRelationCustomer::withIdAndType( + * 4924, + * CustomerType::CUSTOMER + * ); + * $order->status = 'assembling'; + * $order->statusComment = 'Assembling order'; + * $order->weight = 1000; + * $order->shipmentStore = 'main12'; + * $order->shipmentDate = (new DateTime())->add(new DateInterval('P7D')); + * $order->shipped = false; + * $order->customFields = [ + * "galka" => false, + * "test_number" => 0, + * "otpravit_dozakaz" => false, + * ]; + * + * $request->order = $order; + * $request->site = 'moysklad'; + * + * try { + * $response = $client->orders->create($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * printf( + * 'Created order id = %d with following data: %s', + * $response->id, + * print_r($response->order, true) + * ); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Orders\OrdersCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\Orders\OrdersCreateResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function create(OrdersCreateRequest $request): OrdersCreateResponse + { + /** @var OrdersCreateResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'orders/create', + $request, + OrdersCreateResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/orders/fix-external-ids" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\FixExternalRow; + * use RetailCrm\Api\Model\Request\Orders\OrdersFixExternalIdsRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new OrdersFixExternalIdsRequest(); + * $request->orders = [ + * new FixExternalRow(1, 'external_1'), + * new FixExternalRow(2, 'external_2'), + * ]; + * + * try { + * $response = $client->orders->fixExternalIds($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * ``` + * + * @param \RetailCrm\Api\Model\Request\Orders\OrdersFixExternalIdsRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function fixExternalIds(OrdersFixExternalIdsRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'orders/fix-external-ids', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/orders/history" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\Orders\OrderHistoryFilterV4Type; + * use RetailCrm\Api\Model\Request\Orders\OrdersHistoryRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new OrdersHistoryRequest(); + * $request->limit = 100; + * $request->page = 1; + * $request->filter = new OrderHistoryFilterV4Type(); + * $request->filter->sinceId = 2691; + * + * try { + * $response = $client->orders->history($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Orders history: ' . print_r($response->history, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Orders\OrdersHistoryRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Orders\OrdersHistoryResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function history(?OrdersHistoryRequest $request = null): OrdersHistoryResponse + { + /** @var OrdersHistoryResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'orders/history', + $request, + OrdersHistoryResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/orders/links/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Delivery\SerializedEntityOrder; + * use RetailCrm\Api\Model\Entity\Orders\SerializedOrderLink; + * use RetailCrm\Api\Model\Request\Orders\OrdersLinksCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new OrdersLinksCreateRequest(); + * $request->link = new SerializedOrderLink(); + * $request->site = 'aliexpress'; + * $request->link->orders = [ + * SerializedEntityOrder::withNumber('8123522898559160'), + * SerializedEntityOrder::withNumber('8123898472679160') + * ]; + * $request->link->comment = 'same client'; + * + * try { + * $response = $client->orders->linksCreate($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * ``` + * + * @param \RetailCrm\Api\Model\Request\Orders\OrdersLinksCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function linksCreate(OrdersLinksCreateRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'orders/links/create', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/orders/loyalty/apply" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Delivery\SerializedEntityOrder; + * use RetailCrm\Api\Model\Request\Orders\OrdersLoyaltyApplyRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new OrdersLoyaltyApplyRequest(); + * $request->site = 'bitrix-test'; + * $request->order = SerializedEntityOrder::withNumber('7'); + * $request->bonuses = 10; + * + * try { + * $response = $client->orders->loyaltyApply($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Applied loyalty to order: ' . print_r($response->order, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Orders\OrdersLoyaltyApplyRequest $request + * + * @return \RetailCrm\Api\Model\Response\Orders\OrdersLoyaltyApplyResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function loyaltyApply(OrdersLoyaltyApplyRequest $request): OrdersLoyaltyApplyResponse + { + /** @var OrdersLoyaltyApplyResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'orders/loyalty/apply', + $request, + OrdersLoyaltyApplyResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/orders/payments/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Delivery\SerializedEntityOrder; + * use RetailCrm\Api\Model\Entity\Orders\SerializedPayment; + * use RetailCrm\Api\Model\Request\Orders\OrdersPaymentsCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new OrdersPaymentsCreateRequest(); + * $request->payment = new SerializedPayment(); + * $request->payment->type = 'bank-card'; + * $request->payment->amount = 10000; + * $request->payment->comment = 'Comment'; + * $request->payment->order = SerializedEntityOrder::withNumber('8123522898559160'); + * $request->site = 'aliexpress'; + * + * try { + * $response = $client->orders->paymentsCreate($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created payment ID: ' . $response->id; + * ``` + * + * @param \RetailCrm\Api\Model\Request\Orders\OrdersPaymentsCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function paymentsCreate(OrdersPaymentsCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'orders/payments/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/orders/payments/{id}/delete" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->orders->paymentsDelete(4562); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param int $id + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function paymentsDelete(int $id): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'orders/payments/' . $id . '/delete', + null, + SuccessResponse::class + ); + return $response; + } + + + /** + * Makes POST "/api/v5/orders/payments/{id}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Orders\SerializedPayment; + * use RetailCrm\Api\Model\Request\Orders\OrdersPaymentsEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new OrdersPaymentsEditRequest(); + * $request->by = ByIdentifier::ID; + * $request->payment = new SerializedPayment(); + * $request->payment->comment = 'Comment'; + * $request->site = 'aliexpress'; + * + * try { + * $response = $client->orders->paymentsEdit(4562, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Edited payment ID: ' . $response->id; + * ``` + * + * @param int|string $identifier + * @param \RetailCrm\Api\Model\Request\Orders\OrdersPaymentsCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function paymentsEdit($identifier, OrdersPaymentsCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'orders/payments/' . $identifier . '/edit', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/orders/statuses" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Request\Orders\OrdersStatusesRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new OrdersStatusesRequest(); + * $request->externalIds = ['8123522898559160']; + * + * try { + * $response = $client->orders->statuses($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Statuses: ' . print_r($response->orders, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Orders\OrdersStatusesRequest $request + * + * @return \RetailCrm\Api\Model\Response\Orders\OrdersStatusesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function statuses(OrdersStatusesRequest $request): OrdersStatusesResponse + { + /** @var OrdersStatusesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'orders/statuses', + $request, + OrdersStatusesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/orders/upload" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\CountryCodeIso3166; + * use RetailCrm\Api\Enum\Customers\CustomerType; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Orders\Delivery\OrderDeliveryAddress; + * use RetailCrm\Api\Model\Entity\Orders\Delivery\SerializedOrderDelivery; + * use RetailCrm\Api\Model\Entity\Orders\Items\Offer; + * use RetailCrm\Api\Model\Entity\Orders\Items\OrderProduct; + * use RetailCrm\Api\Model\Entity\Orders\Items\PriceType; + * use RetailCrm\Api\Model\Entity\Orders\Items\Unit; + * use RetailCrm\Api\Model\Entity\Orders\Order; + * use RetailCrm\Api\Model\Entity\Orders\Payment; + * use RetailCrm\Api\Model\Entity\Orders\SerializedRelationCustomer; + * use RetailCrm\Api\Model\Request\Orders\OrdersUploadRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new OrdersUploadRequest(); + * $order = new Order(); + * $payment = new Payment(); + * $delivery = new SerializedOrderDelivery(); + * $deliveryAddress = new OrderDeliveryAddress(); + * $offer = new Offer(); + * $item = new OrderProduct(); + * + * $payment->type = 'bank-card'; + * $payment->status = 'paid'; + * $payment->amount = 1000; + * $payment->paidAt = new DateTime(); + * + * $deliveryAddress->index = '344001'; + * $deliveryAddress->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; + * $deliveryAddress->region = 'Ростовская область'; + * $deliveryAddress->city = 'г. Ростов-на-Дону'; + * $deliveryAddress->street = 'ул. Пушкинская'; + * $deliveryAddress->building = '10'; + * + * $delivery->address = $deliveryAddress; + * $delivery->cost = 0; + * $delivery->netCost = 0; + * + * $offer->name = 'Сборка №1445123'; + * $offer->displayName = 'Сборка №1445123'; + * $offer->xmlId = 'tGunLo27jlPGmbA8BrHxY2'; + * $offer->article = '14451445-14451445'; + * $offer->unit = new Unit('796', 'Штука', 'шт'); + * + * $item->offer = $offer; + * $item->priceType = new PriceType('base'); + * $item->quantity = 1; + * $item->purchasePrice = 60; + * + * $order->delivery = $delivery; + * $order->items = [$item]; + * $order->payments = [$payment]; + * $order->orderType = 'test'; + * $order->orderMethod = 'phone'; + * $order->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; + * $order->firstName = 'Test'; + * $order->lastName = 'User'; + * $order->patronymic = 'Patronymic'; + * $order->phone = '89003005069'; + * $order->email = 'testuser12345678901@example.com'; + * $order->managerId = 28; + * $order->customer = SerializedRelationCustomer::withIdAndType( + * 4924, + * CustomerType::CUSTOMER + * ); + * $order->status = 'assembling'; + * $order->statusComment = 'Assembling order'; + * $order->weight = 1000; + * $order->shipmentStore = 'main12'; + * $order->shipmentDate = (new DateTime())->add(new DateInterval('P7D')); + * $order->shipped = false; + * $order->customFields = [ + * "galka" => false, + * "test_number" => 0, + * "otpravit_dozakaz" => false, + * ]; + * + * $request->site = 'moysklad'; + * $request->orders = [$order]; + * + * try { + * $response = $client->orders->upload($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Uploaded: ' . print_r($response->uploadedOrders, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Orders\OrdersUploadRequest $request + * + * @return \RetailCrm\Api\Model\Response\Orders\OrdersUploadResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function upload(OrdersUploadRequest $request): OrdersUploadResponse + { + /** @var OrdersUploadResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'orders/upload', + $request, + OrdersUploadResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/orders" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Request\BySiteRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->orders->get( + * '8123522898559160', + * new BySiteRequest(ByIdentifier::EXTERNAL_ID, 'aliexpress') + * ); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Orders: ' . print_r($response->order, true); + * ``` + * + * @param int|string $identifier + * @param \RetailCrm\Api\Model\Request\BySiteRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Orders\OrdersGetResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function get($identifier, ?BySiteRequest $request): OrdersGetResponse + { + /** @var OrdersGetResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'orders/' . $identifier, + $request, + OrdersGetResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/orders/{externalId}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\ByIdentifier; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Orders\Order; + * use RetailCrm\Api\Model\Request\Orders\OrdersEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $order = new Order(); + * $order->managerComment = 'Manager comment'; + * + * $request = new OrdersEditRequest(); + * $request->by = ByIdentifier::EXTERNAL_ID; + * $request->site = 'aliexpress'; + * $request->order = $order; + * + * try { + * $response = $client->orders->edit('8123522898559160', $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Edited order: ' . print_r($response->order, true); + * ``` + * + * @param int|string $identifier + * @param \RetailCrm\Api\Model\Request\Orders\OrdersEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\Orders\OrdersCreateResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function edit($identifier, OrdersEditRequest $request): OrdersCreateResponse + { + /** @var OrdersCreateResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'orders/' . $identifier . '/edit', + $request, + OrdersCreateResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Packs.php b/src/ResourceGroup/Packs.php new file mode 100644 index 0000000..e0f3e38 --- /dev/null +++ b/src/ResourceGroup/Packs.php @@ -0,0 +1,382 @@ +filter = new OrderProductPackFilter(); + * $request->filter->ids = [143]; + * + * try { + * $response = $client->packs->list($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Packs: ' . print_r($response->packs, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Packs\PacksRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Packs\PacksResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function list(?PacksRequest $request = null): PacksResponse + { + /** @var PacksResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'orders/packs', + $request, + PacksResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/orders/packs/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Packs\OrderProductPack; + * use RetailCrm\Api\Model\Request\Packs\PacksCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $pack = new OrderProductPack(); + * $pack->itemId = 11235; + * $pack->invoiceNumber = '1234567890'; + * $pack->deliveryNoteNumber = '1234567890'; + * $pack->shipmentDate = (new DateTime())->add(new DateInterval('P1D')); + * $pack->store = 'main12'; + * $pack->quantity = 1; + * $pack->purchasePrice = 100; + * + * try { + * $response = $client->packs->create(new PacksCreateRequest($pack)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created pack with ID: ' . $response->id; + * ``` + * + * @param \RetailCrm\Api\Model\Request\Packs\PacksCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function create(PacksCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'orders/packs/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/orders/packs/history" request. + * + * Example: + * ```php + * use RetailCrm\Api\Component\Transformer\DateTimeTransformer; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\Packs\OrderProductPackHistoryFilterType; + * use RetailCrm\Api\Model\Request\Packs\PacksHistoryRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new PacksHistoryRequest(); + * $request->filter = new OrderProductPackHistoryFilterType(); + * $request->filter->startDate = DateTimeTransformer::create('2020-01-01 00:00:00'); + * + * try { + * $response = $client->packs->history($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Packs history: ' . print_r($response->history, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Packs\PacksHistoryRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Packs\PacksHistoryResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function history(?PacksHistoryRequest $request = null): PacksHistoryResponse + { + /** @var PacksHistoryResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'orders/packs/history', + $request, + PacksHistoryResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/orders/packs/{id}" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->packs->get(143); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Pack #143: ' . print_r($response->pack, true); + * ``` + * + * @param int $id + * + * @return \RetailCrm\Api\Model\Response\Packs\PacksGetResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function get(int $id): PacksGetResponse + { + /** @var PacksGetResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'orders/packs/' . $id, + null, + PacksGetResponse::class + ); + return $response; + } + + + /** + * Makes POST "/api/v5/orders/packs/{id}/delete" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->packs->delete(143); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param int $id + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function delete(int $id): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'orders/packs/' . $id . '/delete', + null, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/orders/packs/{id}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Packs\OrderProductPack; + * use RetailCrm\Api\Model\Request\Packs\PacksCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $pack = new OrderProductPack(); + * $pack->shipmentDate = (new DateTime())->add(new DateInterval('P1D')); + * + * try { + * $response = $client->packs->edit(143, new PacksCreateRequest($pack)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Edited pack #' . $response->id; + * ``` + * + * @param int $id + * @param \RetailCrm\Api\Model\Request\Packs\PacksCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function edit(int $id, PacksCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'orders/packs/' . $id . '/edit', + $request, + IdResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Payments.php b/src/ResourceGroup/Payments.php new file mode 100644 index 0000000..09456ec --- /dev/null +++ b/src/ResourceGroup/Payments.php @@ -0,0 +1,208 @@ +invoiceUuid = '5b1b2e9d-b7f1-48f4-acb9-4bfce04b30cf'; + * $request->currency = Currency::RUB; + * $request->amount = .5; + * + * try { + * $response = $client->payments->check(new PaymentCheckRequest($request)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Result: ' . print_r($response->result, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Payments\PaymentCheckRequest $request + * + * @return \RetailCrm\Api\Model\Response\Payments\PaymentCheckResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function check(PaymentCheckRequest $request): PaymentCheckResponse + { + /** @var PaymentCheckResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'payment/check', + $request, + PaymentCheckResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/payment/create-invoice" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Payments\ApiCreateInvoiceRequest; + * use RetailCrm\Api\Model\Request\Payments\PaymentCreateInvoiceRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new ApiCreateInvoiceRequest(); + * $request->paymentId = 4571; + * $request->returnUrl = 'https://example.com'; + * + * try { + * $response = $client->payments->createInvoice(new PaymentCreateInvoiceRequest($request)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Result: ' . print_r($response->result, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Payments\PaymentCreateInvoiceRequest $request + * + * @return \RetailCrm\Api\Model\Response\Payments\PaymentCreateInvoiceResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function createInvoice(PaymentCreateInvoiceRequest $request): PaymentCreateInvoiceResponse + { + /** @var PaymentCreateInvoiceResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'payment/create-invoice', + $request, + PaymentCreateInvoiceResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/payment/update-invoice" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Payments\ApiUpdateInvoiceRequest; + * use RetailCrm\Api\Model\Request\Payments\PaymentUpdateInvoiceRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new ApiUpdateInvoiceRequest(); + * $request->paymentId = 'd6458333-fff3-4fd0-9b23-4e6344451f8e'; + * $request->invoiceUuid = '5b1b2e9d-b7f1-48f4-acb9-4bfce04b30cf'; + * $request->invoiceUrl = 'https://example.com/newUrl'; + * + * try { + * $response = $client->payments->updateInvoice(new PaymentUpdateInvoiceRequest($request)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param \RetailCrm\Api\Model\Request\Payments\PaymentUpdateInvoiceRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function updateInvoice(PaymentUpdateInvoiceRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'payment/update-invoice', + $request, + SuccessResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/References.php b/src/ResourceGroup/References.php new file mode 100644 index 0000000..812bc32 --- /dev/null +++ b/src/ResourceGroup/References.php @@ -0,0 +1,2105 @@ +references->costGroups(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Cost groups: ' . print_r($response->costGroups, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\CostGroupsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function costGroups(): CostGroupsResponse + { + /** @var CostGroupsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/cost-groups', + null, + CostGroupsResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/cost-groups/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\CostGroup; + * use RetailCrm\Api\Model\Request\References\CostGroupsEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new CostGroup(); + * $entity->name = 'Комиссии'; + * $entity->ordering = 60; + * $entity->active = true; + * + * try { + * $response = $client->references->costGroupsEdit('commission', new CostGroupsEditRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\CostGroupsEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function costGroupsEdit(string $code, CostGroupsEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/cost-groups/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/cost-items" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->costItems(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Cost items: ' . print_r($response->costItems, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\CostItemsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function costItems(): CostItemsResponse + { + /** @var CostItemsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/cost-items', + null, + CostItemsResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/cost-items/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\CostItem; + * use RetailCrm\Api\Model\Request\References\CostItemsEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new CostItem(); + * $entity->name = "Test item"; + * $entity->group = "product-cost"; + * $entity->ordering = 990; + * $entity->active = true; + * $entity->appliesToOrders = true; + * $entity->type = "var"; + * $entity->appliesToUsers = false; + * + * try { + * $response = $client->references->costItemsEdit('test-item', new CostItemsEditRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\CostItemsEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function costItemsEdit(string $code, CostItemsEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/cost-items/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/countries" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->countries(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Countries ISO codes: ' . print_r($response->countriesIso, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\CountriesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function countries(): CountriesResponse + { + /** @var CountriesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/countries', + null, + CountriesResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/couriers" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->couriers(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Couriers: ' . print_r($response->couriers, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\CouriersResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function couriers(): CouriersResponse + { + /** @var CouriersResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/couriers', + null, + CouriersResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/couriers/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Orders\Delivery\CourierPhone; + * use RetailCrm\Api\Model\Entity\References\Courier; + * use RetailCrm\Api\Model\Request\References\CouriersCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new Courier(); + * $entity->firstName = 'Tester'; + * $entity->lastName = 'Tester'; + * $entity->phone = new CourierPhone('88005553125'); + * + * try { + * $response = $client->references->couriersCreate(new CouriersCreateRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created courier with ID: ' . $response->id; + * ``` + * + * @param \RetailCrm\Api\Model\Request\References\CouriersCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function couriersCreate(CouriersCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/couriers/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/couriers/{id}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Orders\Delivery\CourierPhone; + * use RetailCrm\Api\Model\Entity\References\Courier; + * use RetailCrm\Api\Model\Request\References\CouriersCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new Courier(); + * $entity->firstName = 'Tester'; + * $entity->lastName = 'Courier'; + * $entity->phone = new CourierPhone('88005553126'); + * + * try { + * $response = $client->references->couriersEdit(1, new CouriersCreateRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param int $id + * @param \RetailCrm\Api\Model\Request\References\CouriersCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function couriersEdit(int $id, CouriersCreateRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/couriers/' . $id . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/delivery-services" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->deliveryServices(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Delivery services: ' . print_r($response->deliveryServices, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\DeliveryServicesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function deliveryServices(): DeliveryServicesResponse + { + /** @var DeliveryServicesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/delivery-services', + null, + DeliveryServicesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/delivery-services/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\DeliveryService; + * use RetailCrm\Api\Model\Request\References\DeliveryServicesEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new DeliveryService(); + * $entity->name = 'dict-deliveryservices-1571123786'; + * $entity->active = false; + * + * try { + * $response = $client->references->deliveryServicesEdit( + * 'dict-deliveryservices-1571123786', + * new DeliveryServicesEditRequest($entity) + * ); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\DeliveryServicesEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function deliveryServicesEdit(string $code, DeliveryServicesEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/delivery-services/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/delivery-types" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->deliveryTypes(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Delivery types: ' . print_r($response->deliveryTypes, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\DeliveryTypesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function deliveryTypes(): DeliveryTypesResponse + { + /** @var DeliveryTypesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/delivery-types', + null, + DeliveryTypesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/delivery-types/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\DeliveryType; + * use RetailCrm\Api\Model\Request\References\DeliveryTypesEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new DeliveryType(); + * $entity->name = 'Test Type'; + * $entity->active = false; + * $entity->defaultCost = 0; + * $entity->defaultNetCost = 0; + * $entity->paymentTypes = [ + * 'bank-card', + * 'bank-transfer', + * 'credit', + * 'cash', + * 'e-money' + * ]; + * + * try { + * $response = $client->references->deliveryTypesEdit('test-type', new DeliveryTypesEditRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\DeliveryTypesEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function deliveryTypesEdit(string $code, DeliveryTypesEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/delivery-types/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/legal-entities" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->legalEntities(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Legal entities: ' . print_r($response->legalEntities, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\LegalEntitiesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function legalEntities(): LegalEntitiesResponse + { + /** @var LegalEntitiesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/legal-entities', + null, + LegalEntitiesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/legal-entities/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\CountryCodeIso3166; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\LegalEntity; + * use RetailCrm\Api\Model\Request\References\LegalEntityEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new LegalEntity(); + * $entity->contragentType = "legal-entity"; + * $entity->legalName = "Test Entity"; + * $entity->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; + * $entity->vatRate = "20.00"; + * + * try { + * $response = $client->references->legalEntitiesEdit('test-entity', new LegalEntityEditRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\LegalEntityEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function legalEntitiesEdit(string $code, LegalEntityEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/legal-entities/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/mg-channels" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->mgChannels(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'MG Channels: ' . print_r($response->mgChannels, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\MgChannelsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function mgChannels(): MgChannelsResponse + { + /** @var MgChannelsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/mg-channels', + null, + MgChannelsResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/order-methods" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->orderMethods(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Order methods: ' . print_r($response->orderMethods, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\OrderMethodsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function orderMethods(): OrderMethodsResponse + { + /** @var OrderMethodsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/order-methods', + null, + OrderMethodsResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/order-methods/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\OrderMethod; + * use RetailCrm\Api\Model\Request\References\OrderMethodsEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new OrderMethod(); + * $entity->name = 'Test Method'; + * $entity->active = true; + * + * try { + * $response = $client->references->orderMethodsEdit('test-method', new OrderMethodsEditRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\OrderMethodsEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function orderMethodsEdit(string $code, OrderMethodsEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/order-methods/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/order-types" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->orderTypes(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Order types: ' . print_r($response->orderTypes, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\OrderTypesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function orderTypes(): OrderTypesResponse + { + /** @var OrderTypesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/order-types', + null, + OrderTypesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/order-types/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\OrderType; + * use RetailCrm\Api\Model\Request\References\OrderTypesEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new OrderType(); + * $entity->name = 'Test Type'; + * $entity->active = true; + * + * try { + * $response = $client->references->orderTypesEdit('test-method', new OrderTypesEditRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\OrderTypesEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function orderTypesEdit(string $code, OrderTypesEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/order-types/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/payment-statuses" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->paymentStatuses(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Payment statuses: ' . print_r($response->paymentStatuses, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\PaymentStatusesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function paymentStatuses(): PaymentStatusesResponse + { + /** @var PaymentStatusesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/payment-statuses', + null, + PaymentStatusesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/payment-statuses/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\PaymentStatus; + * use RetailCrm\Api\Model\Request\References\PaymentStatusesEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new PaymentStatus(); + * $entity->name = 'Test Status'; + * $entity->active = true; + * $entity->ordering = 990; + * $entity->paymentTypes = ['cash']; + * + * try { + * $response = $client->references->paymentStatusesEdit('test-status', new PaymentStatusesEditRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\PaymentStatusesEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function paymentStatusesEdit(string $code, PaymentStatusesEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/payment-statuses/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/payment-types" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->paymentTypes(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Payment types: ' . print_r($response->paymentTypes, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\PaymentTypesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function paymentTypes(): PaymentTypesResponse + { + /** @var PaymentTypesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/payment-types', + null, + PaymentTypesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/payment-types/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\PaymentType; + * use RetailCrm\Api\Model\Request\References\PaymentTypesEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new PaymentType(); + * $entity->name = "Test Integration Payment"; + * $entity->code = "test-payment-integration"; + * $entity->active = true; + * $entity->defaultForCrm = false; + * $entity->defaultForApi = false; + * $entity->paymentStatuses = [ + * "invoice", + * "payment-start", + * "paid", + * "check-refund", + * "check-refund-after" + * ]; + * + * try { + * $response = $client->references->paymentTypesEdit( + * 'test-payment-integration', + * new PaymentTypesEditRequest($entity) + * ); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\PaymentTypesEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function paymentTypesEdit(string $code, PaymentTypesEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/payment-types/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/price-types" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->priceTypes(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Price types: ' . print_r($response->priceTypes, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\PriceTypesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function priceTypes(): PriceTypesResponse + { + /** @var PriceTypesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/price-types', + null, + PriceTypesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/price-types/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\PriceType; + * use RetailCrm\Api\Model\Request\References\PriceTypesEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new PriceType(); + * $entity->name = "Test Price Type"; + * $entity->active = true; + * $entity->ordering = 980; + * + * try { + * $response = $client->references->priceTypesEdit('test-price-type', new PriceTypesEditRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\PriceTypesEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function priceTypesEdit(string $code, PriceTypesEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/price-types/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/product-statuses" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->productStatuses(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Product statuses: ' . print_r($response->productStatuses, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\ProductStatusesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function productStatuses(): ProductStatusesResponse + { + /** @var ProductStatusesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/product-statuses', + null, + ProductStatusesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/product-statuses/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\OrderProductStatus; + * use RetailCrm\Api\Model\Request\References\ProductStatusesEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new OrderProductStatus(); + * $entity->name = "Test Product Status"; + * $entity->active = true; + * $entity->ordering = 980; + * + * try { + * $response = $client->references->productStatusesEdit( + * 'test-product-status', + * new ProductStatusesEditRequest($entity) + * ); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\ProductStatusesEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function productStatusesEdit(string $code, ProductStatusesEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/product-statuses/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/sites" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->sites(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Sites: ' . print_r($response->sites, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\SitesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function sites(): SitesResponse + { + /** @var SitesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/sites', + null, + SitesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/sites/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\CountryCodeIso3166; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\Site; + * use RetailCrm\Api\Model\Request\References\SitesEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new Site(); + * $entity->name = "Test Shop"; + * $entity->url = "https://example.com"; + * $entity->defaultForCrm = false; + * $entity->ymlUrl = "https://example.com/test_catalog.xml"; + * $entity->loadFromYml = true; + * $entity->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; + * $entity->contragentCode = 'test-contragent'; + * + * try { + * $response = $client->references->sitesEdit('test-site', new SitesEditRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\SitesEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function sitesEdit(string $code, SitesEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/sites/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/status-groups" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->statusGroups(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Status groups: ' . print_r($response->statusGroups, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\StatusGroupsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function statusGroups(): StatusGroupsResponse + { + /** @var StatusGroupsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/status-groups', + null, + StatusGroupsResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/statuses" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->statuses(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Statuses: ' . print_r($response->statuses, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\StatusesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function statuses(): StatusesResponse + { + /** @var StatusesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/statuses', + null, + StatusesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/statuses/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\Status; + * use RetailCrm\Api\Model\Request\References\StatusesEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new Status(); + * $entity->name = "Test Status"; + * $entity->active = true; + * $entity->ordering = 990; + * $entity->group = "assembling"; + * + * try { + * $response = $client->references->statusesEdit('test', new StatusesEditRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\StatusesEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function statusesEdit(string $code, StatusesEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/statuses/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/stores" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->stores(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Stores: ' . print_r($response->stores, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\StoresResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function stores(): StoresResponse + { + /** @var StoresResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/stores', + null, + StoresResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/stores/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\Reference\StoreInventoryType; + * use RetailCrm\Api\Enum\Reference\StoreType; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Callback\Entity\Delivery\SerializedStoreWeekOpeningHours; + * use RetailCrm\Api\Model\Callback\Entity\Delivery\StoreWorkTime; + * use RetailCrm\Api\Model\Entity\References\Store; + * use RetailCrm\Api\Model\Entity\References\StoreAddress; + * use RetailCrm\Api\Model\Entity\References\StorePhone; + * use RetailCrm\Api\Model\Request\References\StoresEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new Store(); + * $entity->address = new StoreAddress(); + * $entity->address->countryIso = "UA"; + * $entity->address->region = "Винницкая область"; + * $entity->address->regionId = 104; + * $entity->address->city = "Винница"; + * $entity->address->cityId = 16054; + * $entity->address->cityType = "г."; + * $entity->address->street = "Аграрна"; + * $entity->address->streetId = 1814071; + * $entity->address->streetType = "ул."; + * $entity->address->building = "12"; + * $entity->address->text = "ул. Аграрна, д. 12"; + * $entity->workTime = new SerializedStoreWeekOpeningHours( + * [new StoreWorkTime('9:00', '18:00', '12:00', '13:00')], + * [new StoreWorkTime('9:00', '18:00', '12:00', '13:00')], + * [new StoreWorkTime('9:00', '18:00', '12:00', '13:00')], + * [new StoreWorkTime('9:00', '18:00', '12:00', '13:00')], + * [new StoreWorkTime('9:00', '18:00', '12:00', '13:00')] + * ); + * $entity->type = StoreType::STORE_TYPE_WAREHOUSE; + * $entity->inventoryType = StoreInventoryType::INTEGER; + * $entity->active = true; + * $entity->phone = new StorePhone('88005553123'); + * $entity->name = 'Test Store'; + * + * try { + * $response = $client->references->storesEdit('test', new StoresEditRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\StoresEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function storesEdit(string $code, StoresEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/stores/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/reference/units" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->references->units(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Units: ' . print_r($response->units, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\References\UnitsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function units(): UnitsResponse + { + /** @var UnitsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'reference/units', + null, + UnitsResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/reference/units/{code}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\References\SerializedUnit; + * use RetailCrm\Api\Model\Request\References\UnitsEditRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $entity = new SerializedUnit(); + * $entity->name = "Бобина"; + * $entity->sym = "боб"; + * $entity->active = true; + * + * try { + * $response = $client->references->unitsEdit('nbb', new UnitsEditRequest($entity)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param string $code + * @param \RetailCrm\Api\Model\Request\References\UnitsEditRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function unitsEdit(string $code, UnitsEditRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'reference/units/' . $code . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Segments.php b/src/ResourceGroup/Segments.php new file mode 100644 index 0000000..97c362c --- /dev/null +++ b/src/ResourceGroup/Segments.php @@ -0,0 +1,85 @@ +filter = new SegmentApiFilter(); + * $request->filter->active = NumericBoolean::TRUE; + * $request->filter->minCustomersCount = 700; + * + * try { + * $response = $client->segments->list($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Received segments: ' . print_r($response->segments, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Segments\SegmentsRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Segments\SegmentsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function list(?SegmentsRequest $request = null): SegmentsResponse + { + /** @var SegmentsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'segments', + $request, + SegmentsResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Settings.php b/src/ResourceGroup/Settings.php new file mode 100644 index 0000000..f436947 --- /dev/null +++ b/src/ResourceGroup/Settings.php @@ -0,0 +1,74 @@ +settings->get(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'System settings: ' . print_r($response->settings, true); + * ``` + * + * @return \RetailCrm\Api\Model\Response\Settings\SettingsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function get(): SettingsResponse + { + /** @var SettingsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'settings', + null, + SettingsResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Statistics.php b/src/ResourceGroup/Statistics.php new file mode 100644 index 0000000..ca5f1fc --- /dev/null +++ b/src/ResourceGroup/Statistics.php @@ -0,0 +1,70 @@ +statistics->update(); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function update(): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'statistic/update', + null, + SuccessResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Store.php b/src/ResourceGroup/Store.php new file mode 100644 index 0000000..540e021 --- /dev/null +++ b/src/ResourceGroup/Store.php @@ -0,0 +1,416 @@ +filter = new InventoryFilterType(); + * $request->filter->productActive = NumericBoolean::TRUE; + * $request->filter->sites = ['moysklad', 'aliexpress']; + * + * try { + * $response = $client->store->inventories($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Offers: ' . print_r($response->offers, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Store\InventoriesRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Store\InventoriesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function inventories(?InventoriesRequest $request = null): InventoriesResponse + { + /** @var InventoriesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'store/inventories', + $request, + InventoriesResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/store/inventories/upload" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Store\Inventory; + * use RetailCrm\Api\Model\Entity\Store\Offer; + * use RetailCrm\Api\Model\Request\Store\InventoriesUploadRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $offer = new Offer(); + * $offer->xmlId = '1'; + * $offer->stores = [new Inventory('main12', 15, 15)]; + * + * $request = new InventoriesUploadRequest(); + * $request->offers = [$offer]; + * $request->site = 'aliexpress'; + * + * try { + * $response = $client->store->inventoriesUpload($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Processed offers: ' . $response->processedOffersCount; + * ``` + * + * @param \RetailCrm\Api\Model\Request\Store\InventoriesUploadRequest $request + * + * @return \RetailCrm\Api\Model\Response\Store\InventoriesUploadResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function inventoriesUpload(InventoriesUploadRequest $request): InventoriesUploadResponse + { + /** @var InventoriesUploadResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'store/inventories/upload', + $request, + InventoriesUploadResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/store/prices/upload" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Store\PriceUploadInput; + * use RetailCrm\Api\Model\Entity\Store\PriceUploadPricesInput; + * use RetailCrm\Api\Model\Request\Store\PricesUploadRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $price = new PriceUploadInput(); + * $price->site = 'aliexpress'; + * $price->xmlId = '1'; + * $price->prices = [new PriceUploadPricesInput('base', 100)]; + * + * $request = new PricesUploadRequest([$price]); + * + * try { + * $response = $client->store->pricesUpload($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * printf( + * 'Processed offers: %d, not found offers: %s', + * $response->processedOffersCount, + * print_r($response->notFoundOffers, true) + * ); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Store\PricesUploadRequest $request + * + * @return \RetailCrm\Api\Model\Response\Store\PricesUploadResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function pricesUpload(PricesUploadRequest $request): PricesUploadResponse + { + /** @var PricesUploadResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'store/prices/upload', + $request, + PricesUploadResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/store/product-groups" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\NumericBoolean; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\Store\ProductGroupFilterType; + * use RetailCrm\Api\Model\Request\Store\ProductGroupsRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new ProductGroupsRequest(); + * $request->filter = new ProductGroupFilterType(); + * $request->filter->sites = ['moysklad', 'aliexpress']; + * $request->filter->active = NumericBoolean::TRUE; + * + * try { + * $response = $client->store->productGroups($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Product groups: ' . print_r($response->productGroup, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Store\ProductGroupsRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Store\ProductGroupsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function productGroups(?ProductGroupsRequest $request = null): ProductGroupsResponse + { + /** @var ProductGroupsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'store/product-groups', + $request, + ProductGroupsResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/store/products" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\NumericBoolean; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\Store\ProductFilterType; + * use RetailCrm\Api\Model\Request\Store\ProductsRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new ProductsRequest(); + * $request->filter = new ProductFilterType(); + * $request->filter->active = NumericBoolean::TRUE; + * $request->filter->priceType = 'base'; + * $request->filter->maxPrice = '10000'; + * $request->filter->name = 'Test Product'; + * + * try { + * $response = $client->store->products($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Products: ' . print_r($response->products, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Store\ProductsRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Store\ProductsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function products(?ProductsRequest $request = null): ProductsResponse + { + /** @var ProductsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'store/products', + $request, + ProductsResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/store/products/properties" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\Store\ProductPropertiesFilterType; + * use RetailCrm\Api\Model\Request\Store\ProductPropertiesRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new ProductPropertiesRequest(); + * $request->filter = new ProductPropertiesFilterType(); + * $request->filter->sites = ['moysklad', 'aliexpress']; + * + * try { + * $response = $client->store->productsProperties($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Product properties: ' . print_r($response->properties, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Store\ProductPropertiesRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Store\ProductPropertiesResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function productsProperties(?ProductPropertiesRequest $request = null): ProductPropertiesResponse + { + /** @var ProductPropertiesResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'store/products/properties', + $request, + ProductPropertiesResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Tasks.php b/src/ResourceGroup/Tasks.php new file mode 100644 index 0000000..0f0d21e --- /dev/null +++ b/src/ResourceGroup/Tasks.php @@ -0,0 +1,267 @@ +filter = new TaskFilter(); + * $request->filter->performers = [27]; + * $request->filter->status = TaskStatus::COMPLETED; + * + * try { + * $response = $client->tasks->list($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Tasks: ' . print_r($response->tasks, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Tasks\TasksRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Tasks\TasksResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function list(?TasksRequest $request = null): TasksResponse + { + /** @var TasksResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'tasks', + $request, + TasksResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/tasks/create" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Tasks\AbstractCustomer; + * use RetailCrm\Api\Model\Entity\Tasks\Task; + * use RetailCrm\Api\Model\Request\Tasks\TasksCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new TasksCreateRequest(); + * $request->task = new Task(); + * $request->task->text = 'Test task #1'; + * $request->task->performerId = 27; + * $request->task->customer = new AbstractCustomer(null, 'ru1067815391', 'aliexpress'); + * $request->site = 'aliexpress'; + * + * try { + * $response = $client->tasks->create($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Created task: ' . $response->id; + * ``` + * + * @param \RetailCrm\Api\Model\Request\Tasks\TasksCreateRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function create(TasksCreateRequest $request = null): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'tasks/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/tasks/{id}" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->tasks->get(1); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Task: ' . print_r($response->task, true); + * ``` + * + * @param int $id + * + * @return \RetailCrm\Api\Model\Response\Tasks\TasksGetResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function get(int $id): TasksGetResponse + { + /** @var TasksGetResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'tasks/' . $id, + null, + TasksGetResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/tasks/{id}/edit" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Tasks\Task; + * use RetailCrm\Api\Model\Request\Tasks\TasksCreateRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new TasksCreateRequest(); + * $request->task = new Task(); + * $request->task->text = 'Test task #1 (edited)'; + * $request->site = 'aliexpress'; + * + * try { + * $response = $client->tasks->edit(1, $request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param int $id + * @param \RetailCrm\Api\Model\Request\Tasks\TasksCreateRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function edit(int $id, TasksCreateRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'tasks/' . $id . '/edit', + $request, + SuccessResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Telephony.php b/src/ResourceGroup/Telephony.php new file mode 100644 index 0000000..c905aaf --- /dev/null +++ b/src/ResourceGroup/Telephony.php @@ -0,0 +1,224 @@ +site = 'aliexpress'; + * $event->type = CallEventType::IN; + * $event->phone = '88005553125'; + * $event->userIds = [27]; + * + * try { + * $response = $client->telephony->callEvent(new TelephonyCallEventRequest($event)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Response: ' . print_r($response, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Telephony\TelephonyCallEventRequest $request + * + * @return \RetailCrm\Api\Model\Response\Telephony\CallEventResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function callEvent(TelephonyCallEventRequest $request): CallEventResponse + { + /** @var CallEventResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'telephony/call/event', + $request, + CallEventResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/telephony/calls/upload" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\Telephony\CallEventType; + * use RetailCrm\Api\Enum\Telephony\CallResult; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Entity\Telephony\Call; + * use RetailCrm\Api\Model\Request\Telephony\TelephonyCallsUploadRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $call = new Call(); + * $call->externalId = 'test_call_external_id'; + * $call->date = new DateTime(); + * $call->type = CallEventType::IN; + * $call->phone = '88005553125'; + * $call->userId = 27; + * $call->result = CallResult::ANSWERED; + * $call->duration = 60; + * $call->durationWaiting = 10; + * $call->recordUrl = 'https://examle.com/test.mp3'; + * $call->site = 'aliexpress'; + * + * try { + * $response = $client->telephony->callsUpload(new TelephonyCallsUploadRequest([$call])); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Response: ' . print_r($response, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Telephony\TelephonyCallsUploadRequest $request + * + * @return \RetailCrm\Api\Model\Response\Telephony\CallsUploadResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function callsUpload(TelephonyCallsUploadRequest $request): CallsUploadResponse + { + /** @var CallsUploadResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'telephony/calls/upload', + $request, + CallsUploadResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/telephony/manager" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\NumericBoolean; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Request\Telephony\TelephonyManagerRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new TelephonyManagerRequest(); + * $request->phone = '88005553125'; + * $request->details = NumericBoolean::TRUE; + * $request->ignoreStatus = NumericBoolean::TRUE; + * + * try { + * $response = $client->telephony->manager($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Manager: ' . print_r($response->manager, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Telephony\TelephonyManagerRequest $request + * + * @return \RetailCrm\Api\Model\Response\Telephony\ManagerResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function manager(TelephonyManagerRequest $request): ManagerResponse + { + /** @var ManagerResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'telephony/manager', + $request, + ManagerResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Users.php b/src/ResourceGroup/Users.php new file mode 100644 index 0000000..951be33 --- /dev/null +++ b/src/ResourceGroup/Users.php @@ -0,0 +1,259 @@ +page = 1; + * $request->limit = PaginationLimit::LIMIT_20; + * + * try { + * $response = $client->users->userGroups($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'User groups: ' . print_r($response->groups, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Users\UserGroupsRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Users\UserGroupsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function userGroups(?UserGroupsRequest $request): UserGroupsResponse + { + /** @var UserGroupsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'user-groups', + $request, + UserGroupsResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/users" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\NumericBoolean; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Filter\Users\ApiUserFilter; + * use RetailCrm\Api\Model\Request\Users\UsersRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * $request = new UsersRequest(); + * $request->filter = new ApiUserFilter(); + * $request->filter->active = NumericBoolean::TRUE; + * $request->filter->isAdmin = NumericBoolean::TRUE; + * + * try { + * $response = $client->users->list($request); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Users: ' . print_r($response->users, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Users\UsersRequest|null $request + * + * @return \RetailCrm\Api\Model\Response\Users\UsersResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function list(?UsersRequest $request): UsersResponse + { + /** @var UsersResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'users', + $request, + UsersResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/users/{id}" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->users->get(1); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'User: ' . print_r($response->user, true); + * ``` + * + * @param int $id + * + * @return \RetailCrm\Api\Model\Response\Users\UsersGetResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function get(int $id): UsersGetResponse + { + /** @var UsersGetResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'users/' . $id, + null, + UsersGetResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/users/{id}/status" request. + * + * Example: + * ```php + * use RetailCrm\Api\Enum\Users\UserStatus; + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * use RetailCrm\Api\Model\Request\Users\UsersStatusRequest; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->users->status(1, new UsersStatusRequest(UserStatus::BUSY)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * } + * ``` + * + * @param int $id + * @param \RetailCrm\Api\Model\Request\Users\UsersStatusRequest $request + * + * @return \RetailCrm\Api\Model\Response\SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function status(int $id, UsersStatusRequest $request): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'users/' . $id . '/status', + $request, + SuccessResponse::class + ); + return $response; + } +} diff --git a/src/ResourceGroup/Verification.php b/src/ResourceGroup/Verification.php new file mode 100644 index 0000000..d59951d --- /dev/null +++ b/src/ResourceGroup/Verification.php @@ -0,0 +1,138 @@ +code = 'code'; + * $confirm->checkId = 'checkId'; + * + * try { + * $response = $client->verification->smsConfirm(new SmsVerificationConfirmRequest($confirm)); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Verification: ' . print_r($response->verification, true); + * ``` + * + * @param \RetailCrm\Api\Model\Request\Verification\SmsVerificationConfirmRequest $request + * + * @return \RetailCrm\Api\Model\Response\Verification\SmsVerificationConfirmResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function smsConfirm(SmsVerificationConfirmRequest $request): SmsVerificationConfirmResponse + { + /** @var SmsVerificationConfirmResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'verification/sms/confirm', + $request, + SmsVerificationConfirmResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/verification/sms/{checkId}/status" request. + * + * Example: + * ```php + * use RetailCrm\Api\Factory\SimpleClientFactory; + * use RetailCrm\Api\Interfaces\ApiExceptionInterface; + * + * $client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey'); + * + * try { + * $response = $client->verification->smsStatus('checkId'); + * } catch (ApiExceptionInterface $exception) { + * echo sprintf( + * 'Error from RetailCRM API (status code: %d): %s', + * $exception->getStatusCode(), + * $exception->getMessage() + * ); + * + * if (count($exception->getErrorResponse()->errors) > 0) { + * echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors); + * } + * + * return; + * } + * + * echo 'Verification: ' . print_r($response->verification, true); + * ``` + * + * @param string $checkId + * + * @return \RetailCrm\Api\Model\Response\Verification\SmsVerificationStatusResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + * @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException + * @throws \RetailCrm\Api\Exception\Api\ApiErrorException + * @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException + * @throws \RetailCrm\Api\Exception\Api\MissingParameterException + * @throws \RetailCrm\Api\Exception\Api\ValidationException + * @throws \RetailCrm\Api\Exception\Client\HandlerException + * @throws \RetailCrm\Api\Exception\Client\HttpClientException + */ + public function smsStatus(string $checkId): SmsVerificationStatusResponse + { + /** @var SmsVerificationStatusResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'verification/sms/' . $checkId . '/status', + null, + SmsVerificationStatusResponse::class + ); + return $response; + } +} diff --git a/src/Traits/ApiExceptionFactoryAwareTrait.php b/src/Traits/ApiExceptionFactoryAwareTrait.php new file mode 100644 index 0000000..d815986 --- /dev/null +++ b/src/Traits/ApiExceptionFactoryAwareTrait.php @@ -0,0 +1,35 @@ +apiExceptionFactory = $apiExceptionFactory; + return $this; + } +} diff --git a/src/Traits/EventDispatcherAwareTrait.php b/src/Traits/EventDispatcherAwareTrait.php new file mode 100644 index 0000000..718c50b --- /dev/null +++ b/src/Traits/EventDispatcherAwareTrait.php @@ -0,0 +1,49 @@ +eventDispatcher = $eventDispatcher; + return $this; + } + + /** + * Dispatch an event. + * + * @param object $event + */ + protected function dispatch(object $event): void + { + if (null !== $this->eventDispatcher) { + $this->eventDispatcher->dispatch($event); + } + } +} diff --git a/src/Traits/PsrFactoriesAwareTrait.php b/src/Traits/PsrFactoriesAwareTrait.php new file mode 100644 index 0000000..90d09b1 --- /dev/null +++ b/src/Traits/PsrFactoriesAwareTrait.php @@ -0,0 +1,65 @@ +uriFactory = $uriFactory; + return $this; + } + + /** + * @param \Psr\Http\Message\RequestFactoryInterface $requestFactory + * + * @return self + */ + public function setRequestFactory(RequestFactoryInterface $requestFactory): self + { + $this->requestFactory = $requestFactory; + return $this; + } + + /** + * @param \Psr\Http\Message\StreamFactoryInterface $streamFactory + * + * @return self + */ + public function setStreamFactory(StreamFactoryInterface $streamFactory): self + { + $this->streamFactory = $streamFactory; + return $this; + } +} diff --git a/src/Traits/SerializerAwareTrait.php b/src/Traits/SerializerAwareTrait.php new file mode 100644 index 0000000..fa6392a --- /dev/null +++ b/src/Traits/SerializerAwareTrait.php @@ -0,0 +1,43 @@ +serializer = $serializer; + return $this; + } + + /** + * @return \Liip\Serializer\SerializerInterface + */ + public function getSerializer(): SerializerInterface + { + return $this->serializer; + } +} diff --git a/tests/RetailCrm/Test/TestCase.php b/tests/RetailCrm/Test/TestCase.php deleted file mode 100644 index fa40833..0000000 --- a/tests/RetailCrm/Test/TestCase.php +++ /dev/null @@ -1,95 +0,0 @@ -request), 'client'); - - $property->setAccessible(true); - $property->setValue($client->request, $httpClient); - - return $client; - } - - /** - * Return Client object - * - * @param string $url (default: null) - * @param array $defaultParameters (default: array()) - * - * @return Client - */ - public static function getClient($url = null, $defaultParameters = []) - { - $configUrl = getenv('RETAILCRM_URL') ?: $_SERVER['RETAILCRM_URL']; - $configKey = getenv('RETAILCRM_KEY') ?: $_SERVER['RETAILCRM_KEY']; - $configVersion = getenv('RETAILCRM_VERSION') ?: $_SERVER['RETAILCRM_VERSION']; - - return new Client( - $url ?: $configUrl . '/api/' . $configVersion, - [ - 'apiKey' => array_key_exists('apiKey', $defaultParameters) - ? $defaultParameters['apiKey'] - : $configKey - ] - ); - } -} diff --git a/tests/RetailCrm/Tests/ApiClientTest.php b/tests/RetailCrm/Tests/ApiClientTest.php deleted file mode 100644 index ebb77cc..0000000 --- a/tests/RetailCrm/Tests/ApiClientTest.php +++ /dev/null @@ -1,33 +0,0 @@ - '123']); - - return $client; - } - - /** - * @group client - */ - public function testHttpDebug() - { - $client_v3 = new Client('http://demo.retailcrm.ru/api/v3', ['apiKey' => '123'], true); - $client_v4 = new Client('http://demo.retailcrm.ru/api/v4', ['apiKey' => '123'], true); - $client_v5 = new Client('http://demo.retailcrm.ru/api/v5', ['apiKey' => '123'], true); - - static::assertInstanceOf('RetailCrm\Http\Client', $client_v3); - static::assertInstanceOf('RetailCrm\Http\Client', $client_v4); - static::assertInstanceOf('RetailCrm\Http\Client', $client_v5); - } - - /** - * @group client - * @expectedException \InvalidArgumentException - */ - public function testRequestWrongMethod() - { - $client = static::getClient(); - $client->makeRequest('/a', 'adsf'); - } - - /** - * @group client - * @expectedException \RetailCrm\Exception\CurlException - */ - public function testRequestWrongUrl() - { - $client = new Client('https://asdf.df', []); - $client->makeRequest('/a', Client::METHOD_GET, []); - } - - /** - * @group client - */ - public function testRequestSuccess() - { - $client = static::getClient(); - $response = $client->makeRequest('/orders', Client::METHOD_GET); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertNotEmpty($response->getResponseBody()); - static::assertNotEmpty($response->getResponse()); - static::assertEquals(200, $response->getStatusCode()); - } - - /** - * @group client - */ - public function testRequestSuccessNoDeserialization() - { - $client = static::getClient(); - $response = $client->makeRawRequest('/orders', Client::METHOD_GET); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertNotEmpty($response->getResponseBody()); - static::assertEmpty($response->getResponse()); - static::assertEquals(200, $response->getStatusCode()); - } -} diff --git a/tests/RetailCrm/Tests/Http/RequestOptionsTest.php b/tests/RetailCrm/Tests/Http/RequestOptionsTest.php deleted file mode 100644 index f4b671c..0000000 --- a/tests/RetailCrm/Tests/Http/RequestOptionsTest.php +++ /dev/null @@ -1,25 +0,0 @@ - 'Test']; - $options = new RequestOptions($headers); - - self::assertEquals('X-Test-Header: Test', $options->getHttpHeaders()[0]); - self::assertEquals('Test', $options->getHeaders()['X-Test-Header']); - } - - public function testGetClientTimeout() - { - $options = new RequestOptions([], 10); - - self::assertEquals(10, $options->getClientTimeout()); - } -} diff --git a/tests/RetailCrm/Tests/Methods/CommonMethodsTest.php b/tests/RetailCrm/Tests/Methods/CommonMethodsTest.php deleted file mode 100755 index 210cbb0..0000000 --- a/tests/RetailCrm/Tests/Methods/CommonMethodsTest.php +++ /dev/null @@ -1,77 +0,0 @@ -request->availableVersions(); - - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - static::assertGreaterThan(0, count($response['versions'])); - } - - /** - * Available methods - * - * @group api_methods - * - * @return void - */ - public function testCredentials() - { - $client = static::getApiClient(); - - $response = $client->request->credentials(); - - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - static::assertGreaterThan(0, count($response['credentials'])); - } - - /** - * System settings - * - * @group api_methods - * - * @return void - */ - public function testSettings() - { - $client = static::getApiClient(); - - $response = $client->request->settings(); - - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - static::assertGreaterThan(0, count($response['settings'])); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version4/ApiClientCustomersTest.php b/tests/RetailCrm/Tests/Methods/Version4/ApiClientCustomersTest.php deleted file mode 100644 index b3a5b4f..0000000 --- a/tests/RetailCrm/Tests/Methods/Version4/ApiClientCustomersTest.php +++ /dev/null @@ -1,281 +0,0 @@ -request->customersCreate([ - 'firstName' => self::FIRST_NAME, - 'externalId' => $externalId, - ]); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(201, $response->getStatusCode()); - static::assertTrue(is_int($response['id'])); - - return [ - 'id' => $response['id'], - 'externalId' => $externalId, - ]; - } - - /** - * @group customers_v4 - * @expectedException \InvalidArgumentException - */ - public function testCreateExceptionEmpty() - { - $client = static::getApiClient(null, null, ApiClient::V4); - $client->request->customersCreate([]); - } - - /** - * @group customers - * @depends testCustomersCreate - * - * @param array $ids - * - * @return array - */ - public function testCustomersGet(array $ids) - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->customersGet(678678678); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(404, $response->getStatusCode()); - static::assertFalse($response->isSuccessful()); - - $response = $client->request->customersGet($ids['id'], 'id'); - $customerById = $response['customer']; - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - static::assertEquals(self::FIRST_NAME, $response['customer']['firstName']); - - $response = $client->request->customersGet($ids['externalId'], 'externalId'); - static::assertEquals($customerById['id'], $response['customer']['id']); - - return $ids; - } - - /** - * @group customers_v4 - * @expectedException \InvalidArgumentException - */ - public function testCustomersGetException() - { - $client = static::getApiClient(null, null, ApiClient::V4); - $client->request->customersGet(678678678, 'asdf'); - } - - /** - * @group customers - * @depends testCustomersGet - * - * @param array $ids - */ - public function testCustomersEdit(array $ids) - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->customersEdit( - [ - 'id' => 22342134, - 'lastName' => '12345', - ], - 'id' - ); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(404, $response->getStatusCode()); - - $response = $client->request->customersEdit([ - 'externalId' => $ids['externalId'], - 'lastName' => '12345', - ]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group customers_v4 - * @expectedException \InvalidArgumentException - */ - public function testCustomersEditExceptionEmpty() - { - $client = static::getApiClient(null, null, ApiClient::V4); - $client->request->customersEdit([], 'asdf'); - } - - /** - * @group customers_v4 - * @expectedException \InvalidArgumentException - */ - public function testCustomersEditException() - { - $client = static::getApiClient(null, null, ApiClient::V4); - $client->request->customersEdit(['id' => 678678678], 'asdf'); - } - - /** - * @group customers_v4 - */ - public function testCustomersList() - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->customersList(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful()); - static::assertTrue(isset($response['customers'])); - - $response = $client->request->customersList([], 1, 300); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertFalse( - $response->isSuccessful(), - 'Pagination error' - ); - - $response = $client->request->customersList(['maxOrdersCount' => 10], 1); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue( - $response->isSuccessful(), - 'API returns customers list' - ); - } - - /** - * @group customers_v4 - * @expectedException \InvalidArgumentException - */ - public function testCustomersFixExternalIdsException() - { - $client = static::getApiClient(null, null, ApiClient::V4); - $client->request->customersFixExternalIds([]); - } - - /** - * @group customers_v4 - */ - public function testCustomersFixExternalIds() - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->ordersCreate([ - 'firstName' => 'Aaa111', - ]); - - static::assertTrue( - $response->isSuccessful(), - 'Order created' - ); - - $response = $client->request->ordersGet($response['id'], 'id'); - static::assertTrue( - $response->isSuccessful(), - 'Order fetched' - ); - - $id = $response['order']['customer']['id']; - $externalId = 'asdf' . time(); - - $response = $client->request->customersFixExternalIds([ - ['id' => $id, 'externalId' => $externalId] - ]); - - static::assertTrue( - $response->isSuccessful(), - 'Fixed customer ids' - ); - - $response = $client->request->customersGet($externalId); - static::assertTrue( - $response->isSuccessful(), - 'Got customer' - ); - static::assertEquals( - $id, - $response['customer']['id'], - 'Fixing of customer ids were right' - ); - static::assertEquals( - $externalId, - $response['customer']['externalId'], - 'Fixing of customer ids were right' - ); - } - - /** - * @group customers_v4 - * @expectedException \InvalidArgumentException - */ - public function testCustomersUploadExceptionEmpty() - { - $client = static::getApiClient(null, null, ApiClient::V4); - $client->request->customersUpload([]); - } - - /** - * @group customers_v4 - */ - public function testCustomersUpload() - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $externalIdA = 'upload-a-' . time(); - $externalIdB = 'upload-b-' . time(); - - $response = $client->request->customersUpload([ - [ - 'externalId' => $externalIdA, - 'firstName' => 'Aaa', - ], - [ - 'externalId' => $externalIdB, - 'lastName' => 'Bbb', - ], - ]); - static::assertTrue( - $response->isSuccessful(), - 'Got customer' - ); - static::assertEquals( - $externalIdA, - $response['uploadedCustomers'][0]['externalId'] - ); - static::assertEquals( - $externalIdB, - $response['uploadedCustomers'][1]['externalId'] - ); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version4/ApiClientMarketplaceTest.php b/tests/RetailCrm/Tests/Methods/Version4/ApiClientMarketplaceTest.php deleted file mode 100644 index 99f07af..0000000 --- a/tests/RetailCrm/Tests/Methods/Version4/ApiClientMarketplaceTest.php +++ /dev/null @@ -1,47 +0,0 @@ -request->marketplaceSettingsEdit( - [ - 'name' => self::SNAME, - 'code' => self::SCODE, - 'logo' => 'http://download.retailcrm.pro/logos/setup.svg', - 'active' => 'true' - ] - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - static::assertTrue($response->isSuccessful()); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version4/ApiClientOrdersTest.php b/tests/RetailCrm/Tests/Methods/Version4/ApiClientOrdersTest.php deleted file mode 100644 index 79dccd9..0000000 --- a/tests/RetailCrm/Tests/Methods/Version4/ApiClientOrdersTest.php +++ /dev/null @@ -1,327 +0,0 @@ -request->ordersCreate([ - 'firstName' => self::FIRST_NAME, - 'externalId' => $externalId, - ]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(201, $response->getStatusCode()); - static::assertTrue(is_int($response['id'])); - - return [ - 'id' => $response['id'], - 'externalId' => $externalId, - ]; - } - - /** - * @group orders_v4 - * @expectedException \InvalidArgumentException - */ - public function testOrdersCreateExceptionEmpty() - { - $client = static::getApiClient(null, null, ApiClient::V4); - $client->request->ordersCreate([]); - } - - /** - * @group orders - * @depends testOrdersCreate - * - * @param array $ids - */ - public function testOrdersStatuses(array $ids) - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->ordersStatuses(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(400, $response->getStatusCode()); - static::assertFalse($response->isSuccessful()); - - $response = $client->request->ordersStatuses([], ['asdf']); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - $orders = $response['orders']; - static::assertEquals(0, sizeof($orders)); - - $response = $client->request->ordersStatuses([], [$ids['externalId']]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - $orders = $response['orders']; - static::assertEquals(1, sizeof($orders)); - static::assertEquals('new', $orders[0]['status']); - - $response = $client->request->ordersStatuses([$ids['id']], [$ids['externalId']]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - $orders = $response['orders']; - static::assertEquals(1, sizeof($orders)); - - $response = $client->request->ordersStatuses([$ids['id']]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - $orders = $response['orders']; - static::assertEquals(1, sizeof($orders)); - } - - /** - * @group orders - * @depends testOrdersCreate - * - * @param array $ids - * - * @return array - */ - public function testOrdersGet(array $ids) - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->ordersGet(678678678); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(404, $response->getStatusCode()); - static::assertFalse($response->isSuccessful()); - - $response = $client->request->ordersGet($ids['id'], 'id'); - $orderById = $response['order']; - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - static::assertEquals(self::FIRST_NAME, $response['order']['firstName']); - - $response = $client->request->ordersGet($ids['externalId'], 'externalId'); - static::assertEquals($orderById['id'], $response['order']['id']); - - return $ids; - } - - /** - * @group orders_v4 - * @expectedException \InvalidArgumentException - */ - public function testOrdersGetException() - { - $client = static::getApiClient(null, null, ApiClient::V4); - $client->request->ordersGet(678678678, 'asdf'); - } - - /** - * @group orders - * @depends testOrdersGet - * - * @param array $ids - */ - public function testOrdersEdit(array $ids) - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->ordersEdit( - [ - 'id' => 22342134, - 'lastName' => '12345', - ], - 'id' - ); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(404, $response->getStatusCode()); - - $response = $client->request->ordersEdit([ - 'externalId' => $ids['externalId'], - 'lastName' => '12345', - ]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group orders_v4 - * @expectedException \InvalidArgumentException - */ - public function testOrdersEditExceptionEmpty() - { - $client = static::getApiClient(null, null, ApiClient::V4); - $client->request->ordersEdit([], 'asdf'); - } - - /** - * @group orders_v4 - * @expectedException \InvalidArgumentException - */ - public function testOrdersEditException() - { - $client = static::getApiClient(null, null, ApiClient::V4); - $client->request->ordersEdit(['id' => 678678678], 'asdf'); - } - - /** - * @group orders_v4 - */ - public function testOrdersHistory() - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->ordersHistory(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group orders_v4 - */ - public function testOrdersList() - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->ordersList(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful()); - static::assertTrue(isset($response['orders'])); - - $response = $client->request->ordersList([], 1, 300); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertFalse( - $response->isSuccessful(), - 'Pagination error' - ); - - $response = $client->request->ordersList(['paymentStatus' => 'paid'], 1); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - } - - /** - * @group orders_v4 - * @expectedException \InvalidArgumentException - */ - public function testOrdersFixExternalIdsException() - { - $client = static::getApiClient(null, null, ApiClient::V4); - $client->request->ordersFixExternalIds([]); - } - - /** - * @group orders_v4 - */ - public function testOrdersFixExternalIds() - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->ordersCreate([ - 'firstName' => 'Aaa', - ]); - static::assertTrue( - $response->isSuccessful(), - 'Order created' - ); - - $id = $response['id']; - $externalId = 'asdf' . time(); - - $response = $client->request->ordersFixExternalIds([ - ['id' => $id, 'externalId' => $externalId] - ]); - - static::assertTrue( - $response->isSuccessful(), - 'Fixed order ids' - ); - - $response = $client->request->ordersGet($externalId); - static::assertTrue( - $response->isSuccessful(), - 'Got order' - ); - static::assertEquals( - $id, - $response['order']['id'], - 'Fixing of order ids were right' - ); - static::assertEquals( - $externalId, - $response['order']['externalId'], - 'Fixing of order ids were right' - ); - } - - /** - * @group orders_v4 - * @expectedException \InvalidArgumentException - */ - public function testOrdersUploadExceptionEmpty() - { - $client = static::getApiClient(null, null, ApiClient::V4); - $client->request->ordersUpload([]); - } - - /** - * @group orders_v4 - */ - public function testOrdersUpload() - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $externalIdA = 'upload-a-' . time(); - $externalIdB = 'upload-b-' . time(); - - $response = $client->request->ordersUpload([ - [ - 'externalId' => $externalIdA, - 'firstName' => 'Aaa', - ], - [ - 'externalId' => $externalIdB, - 'lastName' => 'Bbb', - ], - ]); - static::assertTrue( - $response->isSuccessful(), - 'Got order' - ); - static::assertEquals( - $externalIdA, - $response['uploadedOrders'][0]['externalId'] - ); - static::assertEquals( - $externalIdB, - $response['uploadedOrders'][1]['externalId'] - ); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version4/ApiClientPacksTest.php b/tests/RetailCrm/Tests/Methods/Version4/ApiClientPacksTest.php deleted file mode 100644 index 6f335fa..0000000 --- a/tests/RetailCrm/Tests/Methods/Version4/ApiClientPacksTest.php +++ /dev/null @@ -1,62 +0,0 @@ -request->ordersPacksHistory(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->success); - static::assertTrue( - isset($response['history']), - 'API returns orders assembly history' - ); - static::assertTrue( - isset($response['generatedAt']), - 'API returns generatedAt in orders assembly history' - ); - } - - /** - * @group packs_v4 - */ - public function testPacksCreateFailed() - { - $client = static::getApiClient(null, null, "v4"); - $pack = [ - 'itemId' => 12, - 'store' => 'test', - 'quantity' => 2 - ]; - - $response = $client->request->ordersPacksCreate($pack); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(400, $response->getStatusCode()); - static::assertFalse($response->success); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version4/ApiClientPricesTest.php b/tests/RetailCrm/Tests/Methods/Version4/ApiClientPricesTest.php deleted file mode 100644 index 13b447c..0000000 --- a/tests/RetailCrm/Tests/Methods/Version4/ApiClientPricesTest.php +++ /dev/null @@ -1,108 +0,0 @@ -request->pricesTypesEdit( - [ - 'code' => 'sample_v4_price_code', - 'name' => 'Sample v4 price type', - 'ordering' => 500, - 'active' => true - ] - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - static::assertTrue($response->isSuccessful()); - } - - /** - * @depends testPricesEdit - * @group prices_v4 - */ - public function testPricesGet() - { - $client = static::getApiClient(null, null, "v4"); - - $response = $client->request->pricesTypes(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - - - /** - * @group prices_v4 - * @expectedException \InvalidArgumentException - */ - public function testPricesUploadExceptionEmpty() - { - $client = static::getApiClient(null, null, "v4"); - $client->request->storePricesUpload([]); - } - - /** - * @depends testPricesEdit - * @group prices_v4 - */ - public function testPricesUpload() - { - $client = static::getApiClient(null, null, "v4"); - - $xmlIdA = 'upload-a-' . time(); - $xmlIdB = 'upload-b-' . time(); - - $response = $client->request->storePricesUpload([ - [ - 'xmlId' => $xmlIdA, - 'prices' => [ - [ - 'code' => 'sample_v4_price_code', - 'price' => 1700 - ] - ] - ], - [ - 'xmlId' => $xmlIdB, - 'prices' => [ - [ - 'code' => 'sample_v4_price_code', - 'price' => 1500 - ] - ] - ], - ]); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version4/ApiClientReferenceTest.php b/tests/RetailCrm/Tests/Methods/Version4/ApiClientReferenceTest.php deleted file mode 100644 index d7b8b5a..0000000 --- a/tests/RetailCrm/Tests/Methods/Version4/ApiClientReferenceTest.php +++ /dev/null @@ -1,168 +0,0 @@ -request->$method(); - - /* @var \RetailCrm\Response\ApiResponse $response */ - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful()); - static::assertTrue(isset($response[$name])); - static::assertTrue(is_array($response[$name])); - } - - /** - * @group reference_v4 - * @dataProvider getEditDictionaries - * @expectedException \InvalidArgumentException - * - * @param $name - */ - public function testEditingException($name) - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $method = $name . 'Edit'; - $client->request->$method([]); - } - - /** - * @group reference_v4 - * @dataProvider getEditDictionaries - * - * @param $name - */ - public function testEditing($name) - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $code = 'dict-' . strtolower($name) . '-' . time(); - $method = $name . 'Edit'; - $params = [ - 'code' => $code, - 'name' => 'Aaa' . $code, - 'active' => false - ]; - if ($name == 'statuses') { - $params['group'] = 'new'; - } - - $response = $client->request->$method($params); - /* @var \RetailCrm\Response\ApiResponse $response */ - - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - - $response = $client->request->$method([ - 'code' => $code, - 'name' => 'Bbb' . $code, - 'active' => false - ]); - - sleep(1); - - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - } - - /** - * @group reference_v4 - * @group site - */ - public function testSiteEditing() - { - $name = 'sites'; - $client = static::getApiClient(null, null, ApiClient::V4); - - $code = 'dict-' . strtolower($name) . '-' . time(); - $method = $name . 'Edit'; - $params = [ - 'code' => $code, - 'name' => 'Aaa', - 'active' => false - ]; - - $response = $client->request->$method($params); - /* @var \RetailCrm\Response\ApiResponse $response */ - - static::assertEquals(400, $response->getStatusCode()); - - if ($code == $client->request->getSite()) { - $method = $name . 'Edit'; - $params = [ - 'code' => $code, - 'name' => 'Aaa' . time(), - 'active' => false - ]; - - $response = $client->request->$method($params); - static::assertEquals(200, $response->getStatusCode()); - } - } - - /** - * @return array - */ - public function getListDictionaries() - { - return [ - ['deliveryServices'], - ['deliveryTypes'], - ['orderMethods'], - ['orderTypes'], - ['paymentStatuses'], - ['paymentTypes'], - ['productStatuses'], - ['statusGroups'], - ['statuses'], - ['sites'], - ['stores'], - ]; - } - - /** - * @return array - */ - public function getEditDictionaries() - { - return [ - ['deliveryServices'], - ['deliveryTypes'], - ['orderMethods'], - ['orderTypes'], - ['paymentStatuses'], - ['paymentTypes'], - ['productStatuses'], - ['statuses'], - ]; - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version4/ApiClientStoreTest.php b/tests/RetailCrm/Tests/Methods/Version4/ApiClientStoreTest.php deleted file mode 100644 index 5297d9a..0000000 --- a/tests/RetailCrm/Tests/Methods/Version4/ApiClientStoreTest.php +++ /dev/null @@ -1,141 +0,0 @@ -request->storesEdit(['name' => self::SNAME, 'code' => self::SCODE]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group store_v4 - */ - public function testStoreInventories() - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->storeInventories(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - static::assertTrue( - isset($response['offers']), - 'API returns orders assembly history' - ); - } - - /** - * @group store_v4 - * @expectedException \InvalidArgumentException - */ - public function testInventoriesException() - { - $client = static::getApiClient(null, null, ApiClient::V4); - $client->request->storeInventoriesUpload([]); - } - - /** - * @group store_v4 - */ - public function testInventoriesUpload() - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->storeInventoriesUpload([ - [ - 'externalId' => 'pTKIKAeghYzX21HTdzFCe1', - 'stores' => [ - [ - 'code' => self::SCODE, - 'available' => 10, - 'purchasePrice' => 1700 - ] - ] - ], - [ - 'externalId' => 'JQIvcrCtiSpOV3AAfMiQB3', - 'stores' => [ - [ - 'code' => self::SCODE, - 'available' => 20, - 'purchasePrice' => 1500 - ] - ] - ], - ]); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group integration - */ - public function testInventoriesFailed() - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $externalIdA = 'upload-a-' . time(); - $externalIdB = 'upload-b-' . time(); - - $response = $client->request->storeInventoriesUpload([ - [ - 'externalId' => $externalIdA, - 'available' => 10, - 'purchasePrice' => 1700 - ], - [ - 'externalId' => $externalIdB, - 'available' => 20, - 'purchasePrice' => 1500 - ], - ]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(400, $response->getStatusCode()); - static::assertTrue(isset($response['errorMsg']), $response['errorMsg']); - } - - /** - * @group store_v4 - */ - public function testStoreProducts() - { - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->storeProducts(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version4/ApiClientTelephonyTest.php b/tests/RetailCrm/Tests/Methods/Version4/ApiClientTelephonyTest.php deleted file mode 100644 index ab7b6fd..0000000 --- a/tests/RetailCrm/Tests/Methods/Version4/ApiClientTelephonyTest.php +++ /dev/null @@ -1,185 +0,0 @@ -request->telephonySettingsEdit( - self::TEL_CODE, - self::TEL_CLIENT, - true, - 'TestTelephonyV4', - false, - self::TEL_IMAGE, - [['userId' => $user, 'code' => '101']], - [['siteCode' => 'api-client-php', 'externalPhone' => '+74950000000']] - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - static::assertTrue($response->isSuccessful()); - } - - /** - * Settings Get test - * - * @group telephony - * - * @return void - */ - public function testTelephonySettingsGet() - { - self::markTestSkipped('Should be fixed.'); - $client = static::getApiClient(null, null, ApiClient::V4); - - /* @var \RetailCrm\Response\ApiResponse $response */ - $response = $client->request->telephonySettingsGet(self::TEL_CODE); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * Event test - * - * @group telephony - * - * @return void - */ - public function testTelephonyEvent() - { - $stub = $this->getMockBuilder(\RetailCrm\Http\Client::class) - ->disableOriginalConstructor() - ->setMethods(['makeRequest']) - ->getMock(); - - $parameters = [ - 'phone' => '+79999999999', - 'type' => 'in', - 'codes' => ['101'], - 'userIds' => [2], - 'hangupStatus' => 'failed', - 'callExternalId' => '+74950000000', - 'externalPhone' => '123456789', - 'webAnalyticsData' => [], - 'site' => 'retailcrm-ru' - ]; - - $stub->expects(self::once())->method('makeRequest')->with( - '/telephony/call/event', - "POST", - ['event' => json_encode($parameters)] - )->willReturn((new ApiResponse(200, json_encode(['success' => true])))->asJsonResponse()); - - $client = static::getMockedApiClient($stub); - - $response = $client->request->telephonyCallEvent( - '+79999999999', - 'in', - ['101'], - [2], - 'failed', - '123456789', - '+74950000000', - [] - ); - - static::assertTrue($response->isSuccessful()); - } - - /** - * Upload test - * - * @group telephony - * - * @return void - */ - public function testTelephonyUpload() - { - self::markTestSkipped('Should be fixed.'); - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->telephonyCallsUpload( - [ - [ - 'date' => '2016-07-22 00:18:00', - 'type' => 'in', - 'phone' => '+79999999999', - 'code' => '101', - 'result' => 'answered', - 'externalId' => rand(10, 100), - 'recordUrl' => 'http://download.retailcrm.pro/api-client-files/beep1.mp3' - ], - [ - 'date' => '2016-07-22 00:24:00', - 'type' => 'in', - 'phone' => '+79999999999', - 'code' => '101', - 'result' => 'answered', - 'externalId' => rand(10, 100), - 'recordUrl' => 'http://download.retailcrm.pro/api-client-files/beep2.mp3' - ] - ] - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * Manager test - * - * @group telephony - * - * @return void - */ - public function testTelephonyManager() - { - self::markTestSkipped('Should be fixed.'); - $client = static::getApiClient(null, null, ApiClient::V4); - - $response = $client->request->telephonyCallManager('+79999999999', 1); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version4/ApiClientUsersTest.php b/tests/RetailCrm/Tests/Methods/Version4/ApiClientUsersTest.php deleted file mode 100644 index 9fe7ff9..0000000 --- a/tests/RetailCrm/Tests/Methods/Version4/ApiClientUsersTest.php +++ /dev/null @@ -1,63 +0,0 @@ -request->usersGroups(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group users_v4 - */ - public function testUsersList() - { - $client = static::getApiClient(null, null, "v4"); - - $response = $client->request->usersList(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group users_v4 - */ - public function testUsersGet() - { - $client = static::getApiClient(null, null, "v4"); - $user = getenv('RETAILCRM_USER') ?: $_SERVER['RETAILCRM_USER']; - - $response = $client->request->usersGet($user); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - static::assertTrue($response->isSuccessful()); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version5/ApiClientCustomersCorporateTest.php b/tests/RetailCrm/Tests/Methods/Version5/ApiClientCustomersCorporateTest.php deleted file mode 100644 index 9b65db0..0000000 --- a/tests/RetailCrm/Tests/Methods/Version5/ApiClientCustomersCorporateTest.php +++ /dev/null @@ -1,666 +0,0 @@ -request->customersCorporateCreate([ - 'nickName' => self::NICK_NAME, - 'externalId' => $externalId - ]); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(201, $response->getStatusCode()); - static::assertTrue(is_int($response['id'])); - - return [ - 'id' => $response['id'], - 'externalId' => $externalId, - ]; - } - - /** - * @group customers_corporate_v5 - * @expectedException \InvalidArgumentException - */ - public function testCreateExceptionEmpty() - { - $client = static::getApiClient(); - $client->request->customersCorporateCreate([]); - } - - /** - * @group customers_corporate_v5 - * @depends testCustomersCorporateCreate - * - * @param array $ids - * - * @return array - */ - public function testCustomersCorporateGet(array $ids) - { - $client = static::getApiClient(); - - $response = $client->request->customersCorporateGet(678678678); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(404, $response->getStatusCode()); - static::assertFalse($response->isSuccessful()); - - $response = $client->request->customersCorporateGet($ids['id'], 'id'); - $customerById = $response['customerCorporate']; - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - static::assertEquals(self::NICK_NAME, $response['customerCorporate']['nickName']); - - $response = $client->request->customersCorporateGet($ids['externalId'], 'externalId'); - static::assertEquals($customerById['id'], $response['customerCorporate']['id']); - - return $ids; - } - - /** - * @group customers_corporate_v5 - * @expectedException \InvalidArgumentException - */ - public function testCustomersCorporateGetException() - { - $client = static::getApiClient(); - $client->request->customersCorporateGet(678678678, 'asdf'); - } - - /** - * @group customers_corporate_v5 - * @depends testCustomersCorporateGet - * - * @param array $ids - * - * @return array - */ - public function testCustomersCorporateEdit(array $ids) - { - $client = static::getApiClient(); - - $response = $client->request->customersCorporateEdit( - [ - 'id' => 22342134, - 'nickName' => '12345', - ], - 'id' - ); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(404, $response->getStatusCode()); - - $response = $client->request->customersCorporateEdit([ - 'externalId' => $ids['externalId'], - 'mainAddress' => ['name' => '12345'], - ]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - - return $ids; - } - - /** - * testCustomersCorporateEditException - * @expectedException \InvalidArgumentException - */ - public function testCustomersCorporateEditExceptionNoExternalId() - { - $client = static::getApiClient(); - $client->request->customersCorporateEdit(['id' => 0]); - } - - /** - * @group customers_corporate_v5 - * @expectedException \InvalidArgumentException - */ - public function testCustomersCorporateEditExceptionEmpty() - { - $client = static::getApiClient(); - $client->request->customersCorporateEdit([], 'asdf'); - } - - /** - * @group customers_corporate_v5 - * @expectedException \InvalidArgumentException - */ - public function testCustomersCorporateEditException() - { - $client = static::getApiClient(); - $client->request->customersCorporateEdit(['id' => 678678678], 'asdf'); - } - - /** - * @group customers_corporate_v5 - */ - public function testCustomersCorporateList() - { - $client = static::getApiClient(); - - $response = $client->request->customersCorporateList(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful()); - static::assertTrue(isset($response['customersCorporate'])); - - $response = $client->request->customersCorporateList([], 1, 300); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertFalse( - $response->isSuccessful(), - 'Pagination error' - ); - - $response = $client->request->customersCorporateList(['maxOrdersCount' => 10], 1); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue( - $response->isSuccessful(), - 'API returns customers list' - ); - } - - /** - * @group customers_corporate_v5 - * @expectedException \InvalidArgumentException - */ - public function testCustomersCorporateFixExternalIdsException() - { - $client = static::getApiClient(); - $client->request->customersCorporateFixExternalIds([]); - } - - /** - * @group customers_corporate_v5 - */ - public function testCustomersCorporateFixExternalIds() - { - $client = static::getApiClient(); - - $response = $client->request->ordersCreate([ - 'firstName' => 'Aaa111', - ]); - - static::assertTrue( - $response->isSuccessful(), - 'Order created' - ); - - $response = $client->request->ordersGet($response['id'], 'id'); - static::assertTrue( - $response->isSuccessful(), - 'Order fetched' - ); - - $id = $response['order']['customer']['id']; - $externalId = 'asdf' . time(); - - $response = $client->request->customersCorporateFixExternalIds([ - ['id' => $id, 'externalId' => $externalId] - ]); - - static::assertTrue( - $response->isSuccessful(), - 'Fixed customer ids' - ); - - $response = $client->request->customersCorporateGet($externalId); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - } - - /** - * @group customers_corporate_v5 - * @expectedException \InvalidArgumentException - */ - public function testCustomersCorporateUploadExceptionEmpty() - { - $client = static::getApiClient(); - $client->request->customersCorporateUpload([]); - } - - /** - * @group customers_corporate_v5 - */ - public function testCustomersCorporateUpload() - { - $client = static::getApiClient(); - - $externalIdA = 'upload-a-' . time(); - $externalIdB = 'upload-b-' . time(); - - $response = $client->request->customersCorporateUpload([ - [ - 'externalId' => $externalIdA, - 'nickName' => 'Aaa', - ], - [ - 'externalId' => $externalIdB, - 'nickName' => 'Bbb', - ], - ]); - static::assertTrue( - $response->isSuccessful(), - 'Got customer' - ); - static::assertEquals( - $externalIdA, - $response['uploadedCustomers'][0]['externalId'] - ); - static::assertEquals( - $externalIdB, - $response['uploadedCustomers'][1]['externalId'] - ); - } - - /** - * testCustomersCorporateAddressesException - * @expectedException \InvalidArgumentException - */ - public function testCustomersCorporateAddressesException() - { - $client = static::getApiClient(); - $client->request->customersCorporateAddresses('', [], [], [], 'uid'); - } - - /** - * testCustomersCorporateAddresses - * - * @param array $ids - * - * @group customers_corporate_v5 - * @depends testCustomersCorporateEdit - * @return array - */ - public function testCustomersCorporateAddresses(array $ids) - { - $client = static::getApiClient(); - $response = $client->request->customersCorporateAddresses($ids['externalId'], ['name' => 'name'], 1, 20); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue( - $response->isSuccessful(), - 'API returns addresses list' - ); - - return $ids; - } - - /** - * @group customers_corporate_v5 - */ - public function testCustomersCorporateHistory() - { - $client = static::getApiClient(); - $response = $client->request->customersCorporateHistory(['sinceId' => 1], 1, 20); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue( - $response->isSuccessful(), - 'API returns history list' - ); - } - - /** - * @group customers_corporate_v5 - */ - public function testCustomersCorporateNotesList() - { - $client = static::getApiClient(); - $response = $client->request->customersCorporateNotesList(['text' => 'text'], 1, 20); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue( - $response->isSuccessful(), - 'API returns notes list' - ); - } - - /** - * @group customers_corporate_v5 - * @depends testCustomersCorporateAddresses - * - * @param array $ids - * - * @return array - */ - public function testCustomersCorporateCompaniesList(array $ids) - { - $client = static::getApiClient(); - $response = $client->request->customersCorporateCompanies($ids['externalId'], ['ids' => [1]], 1, 20); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue( - $response->isSuccessful(), - 'API returns companies list' - ); - - return $ids; - } - - /** - * @group customers_corporate_v5 - * - * @param array $ids - * @depends testCustomersCorporateCompaniesList - * - * @return array - */ - public function testCustomersCorporateContactsList(array $ids) - { - $client = static::getApiClient(); - $response = $client->request->customersCorporateContacts($ids['externalId'], ['ids' => [1]], 1, 20); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue( - $response->isSuccessful(), - 'API returns contacts list' - ); - - return $ids; - } - - /** - * testCustomersCorporateNotesCreate - * - * @param array $ids - * - * @depends testCustomersCorporateContactsList - * @return array - */ - public function testCustomersCorporateNotesCreate(array $ids) - { - $client = static::getApiClient(); - $response = $client->request->customersCorporateNotesCreate([ - 'text' => 'test note', - 'customer' => [ - 'externalId' => $ids['externalId'] - ] - ]); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(201, $response->getStatusCode()); - static::assertArrayHasKey('id', $response->getResponse()); - - return array_merge($response->getResponse(), ['customer' => $ids]); - } - - /** - * testCustomersCorporateNotesCreateException - * @expectedException \InvalidArgumentException - */ - public function testCustomersCorporateNotesCreateException() - { - $client = static::getApiClient(); - $client->request->customersCorporateNotesCreate([ - 'text' => 'test note', - 'customer' => [] - ]); - } - - /** - * testCustomersCorporateNotesDelete - * - * @param array $noteResponse - * - * @depends testCustomersCorporateNotesCreate - * @return mixed - */ - public function testCustomersCorporateNotesDelete(array $noteResponse) - { - $client = static::getApiClient(); - $response = $client->request->customersCorporateNotesDelete($noteResponse['id']); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - - return $noteResponse['customer']; - } - - /** - * testCustomersCorporateNotesDeleteException - * @expectedException \InvalidArgumentException - */ - public function testCustomersCorporateNotesDeleteException() - { - $client = static::getApiClient(); - $client->request->customersCorporateNotesDelete(null); - } - - /** - * testCustomersCorporateAddressCreate - * - * @param array $ids - * @depends testCustomersCorporateNotesDelete - * - * @return array - */ - public function testCustomersCorporateAddressCreate(array $ids) - { - $client = static::getApiClient(); - $response = $client->request->customersCorporateAddressesCreate( - $ids['externalId'], - ['text' => 'Boldovo, Ruzayevsky District, Respublika Mordoviya'] - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful(), 'Address is created'); - static::assertArrayHasKey('id', $response->getResponse()); - - return array_merge($response->getResponse(), ['customer' => $ids]); - } - - /** - * testCustomersCorporateAddressEdit - * - * @param array $createResponse - * @depends testCustomersCorporateAddressCreate - * - * @return array - */ - public function testCustomersCorporateAddressEdit(array $createResponse) - { - $client = static::getApiClient(); - $response = $client->request->customersCorporateAddressesEdit( - $createResponse['customer']['externalId'], - $createResponse['id'], - ['text' => '648593, Evenkiysky District, Krasnoyarsk Krai'], - 'externalId', - 'id' - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful(), 'Address is edited'); - - return $createResponse['customer']; - } - - /** - * testCustomersCorporateAddressesEditException - * @expectedException \InvalidArgumentException - */ - public function testCustomersCorporateAddressesEditException() - { - $client = static::getApiClient(); - $client->request->customersCorporateAddressesEdit(0, 0, []); - } - - /** - * testCustomersCorporateCompaniesCreate - * - * @param array $ids - * @depends testCustomersCorporateAddressEdit - * - * @return array - */ - public function testCustomersCorporateCompaniesCreate(array $ids) - { - $client = static::getApiClient(); - $response = $client->request->customersCorporateCompaniesCreate( - $ids['externalId'], - [ - 'active' => true, - 'name' => 'Company name', - 'brand' => 'company brand', - 'contragent' => [ - 'contragentType' => 'legal-entity', - 'legalName' => 'Company Name', - 'legalAddress' => '648593, Evenkiysky District, Krasnoyarsk Krai', - 'INN' => '000000000', - 'OKPO' => '000000000', - 'KPP' => '000000000', - 'OGRN' => '000000000', - 'BIK' => '000000000', - 'bank' => 'bank', - 'bankAddress' => 'bank address', - 'corrAccount' => 'correspondent account', - 'bankAccount' => 'bank account' - ] - ] - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful(), 'Company is created'); - static::assertArrayHasKey('id', $response->getResponse()); - - return array_merge($response->getResponse(), ['customer' => $ids]); - } - - /** - * testCustomersCorporateCompaniesEdit - * - * @depends testCustomersCorporateCompaniesCreate - * - * @param array $createResp - * - * @return mixed - */ - public function testCustomersCorporateCompaniesEdit(array $createResp) - { - $client = static::getApiClient(); - $response = $client->request->customersCorporateCompaniesEdit( - $createResp['customer']['externalId'], - $createResp['id'], - ['name' => 'Company Name 2'], - 'externalId', - 'id' - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful(), 'Company is edited'); - static::assertArrayHasKey('id', $response->getResponse()); - - return $createResp['customer']; - } - - /** - * testCustomersCorporateContactsCreate - * - * @param array $ids - * @depends testCustomersCorporateCompaniesEdit - * - * @return array - */ - public function testCustomersCorporateContactsCreate(array $ids) - { - $client = static::getApiClient(); - $testCustomerExternalId = sprintf('test-customer-external-id-%d', time()); - $customerResponse = $client->request->customersCreate([ - 'firstName' => 'Test Customer', - 'externalId' => $testCustomerExternalId, - ]); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $customerResponse); - static::assertTrue($customerResponse->isSuccessful(), 'Test customer is created'); - - $response = $client->request->customersCorporateContactsCreate( - $ids['externalId'], - [ - 'customer' => [ - 'externalId' => $testCustomerExternalId, - 'browserId' => 'ca205b35862546758218cac776355f32', - 'site' => static::getSite() - ] - ] - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful(), 'Contact person is created'); - static::assertArrayHasKey('id', $response->getResponse()); - - return [ - 'contact' => $customerResponse->getResponse(), - 'customer' => $ids - ]; - } - - /** - * testCustomersCorporateContactsEdit - * - * @depends testCustomersCorporateContactsCreate - * - * @param array $createResp - * - * @return mixed - */ - public function testCustomersCorporateContactsEdit(array $createResp) - { - $client = static::getApiClient(); - $response = $client->request->customersCorporateContactsEdit( - $createResp['customer']['externalId'], - $createResp['contact']['id'], - [ - 'id' => $createResp['contact']['id'], - 'customer' => [ - 'browserId' => '73997eedbdaf4c0991b1a5511aeae407', - 'site' => static::getSite() - ] - ], - 'externalId', - 'id', - static::getSite() - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful(), 'Contact person is edited'); - static::assertArrayHasKey('id', $response->getResponse()); - - return $createResp['customer']; - } - - /** - * getSite - * - * @return string - */ - private static function getSite() - { - return getenv('RETAILCRM_SITE') ?: $_SERVER['RETAILCRM_SITE']; - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version5/ApiClientCustomersTest.php b/tests/RetailCrm/Tests/Methods/Version5/ApiClientCustomersTest.php deleted file mode 100644 index 5353d1e..0000000 --- a/tests/RetailCrm/Tests/Methods/Version5/ApiClientCustomersTest.php +++ /dev/null @@ -1,444 +0,0 @@ -request->customersCreate([ - 'firstName' => self::FIRST_NAME, - 'externalId' => $externalId, - ]); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(201, $response->getStatusCode()); - static::assertTrue(is_int($response['id'])); - - return [ - 'id' => $response['id'], - 'externalId' => $externalId, - ]; - } - - /** - * @group customers_v5 - * @expectedException \InvalidArgumentException - */ - public function testCreateExceptionEmpty() - { - $client = static::getApiClient(); - $client->request->customersCreate([]); - } - - /** - * @group customers_v5 - * @depends testCustomersCreate - * - * @param array $ids - * - * @return array - */ - public function testCustomersGet(array $ids) - { - $client = static::getApiClient(); - - $response = $client->request->customersGet(678678678); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(404, $response->getStatusCode()); - static::assertFalse($response->isSuccessful()); - - $response = $client->request->customersGet($ids['id'], 'id'); - $customerById = $response['customer']; - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - static::assertEquals(self::FIRST_NAME, $response['customer']['firstName']); - - $response = $client->request->customersGet($ids['externalId'], 'externalId'); - static::assertEquals($customerById['id'], $response['customer']['id']); - - return $ids; - } - - /** - * @group customers_v5 - * @expectedException \InvalidArgumentException - */ - public function testCustomersGetException() - { - $client = static::getApiClient(); - $client->request->customersGet(678678678, 'asdf'); - } - - /** - * @group customers_v5 - * @depends testCustomersGet - * - * @param array $ids - */ - public function testCustomersEdit(array $ids) - { - $client = static::getApiClient(); - - $response = $client->request->customersEdit( - [ - 'id' => 22342134, - 'lastName' => '12345', - ], - 'id' - ); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(404, $response->getStatusCode()); - - $response = $client->request->customersEdit([ - 'externalId' => $ids['externalId'], - 'lastName' => '12345', - ]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group customers_v5 - * @expectedException \InvalidArgumentException - */ - public function testCustomersEditExceptionEmpty() - { - $client = static::getApiClient(); - $client->request->customersEdit([], 'asdf'); - } - - /** - * @group customers_v5 - * @expectedException \InvalidArgumentException - */ - public function testCustomersEditException() - { - $client = static::getApiClient(); - $client->request->customersEdit(['id' => 678678678], 'asdf'); - } - - /** - * @group customers_v5 - */ - public function testCustomersList() - { - $client = static::getApiClient(); - - $response = $client->request->customersList(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful()); - static::assertTrue(isset($response['customers'])); - - $response = $client->request->customersList([], 1, 300); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertFalse( - $response->isSuccessful(), - 'Pagination error' - ); - - $response = $client->request->customersList(['maxOrdersCount' => 10], 1); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue( - $response->isSuccessful(), - 'API returns customers list' - ); - } - - /** - * @group customers_v5 - * @expectedException \InvalidArgumentException - */ - public function testCustomersFixExternalIdsException() - { - $client = static::getApiClient(); - $client->request->customersFixExternalIds([]); - } - - /** - * @group customers_v5 - */ - public function testCustomersFixExternalIds() - { - $client = static::getApiClient(); - - $response = $client->request->ordersCreate([ - 'firstName' => 'Aaa111', - ]); - - static::assertTrue( - $response->isSuccessful(), - 'Order created' - ); - - $response = $client->request->ordersGet($response['id'], 'id'); - static::assertTrue( - $response->isSuccessful(), - 'Order fetched' - ); - - $id = $response['order']['customer']['id']; - $externalId = 'asdf' . time(); - - $response = $client->request->customersFixExternalIds([ - ['id' => $id, 'externalId' => $externalId] - ]); - - static::assertTrue( - $response->isSuccessful(), - 'Fixed customer ids' - ); - - $response = $client->request->customersGet($externalId); - static::assertTrue( - $response->isSuccessful(), - 'Got customer' - ); - static::assertEquals( - $id, - $response['customer']['id'], - 'Fixing of customer ids were right' - ); - static::assertEquals( - $externalId, - $response['customer']['externalId'], - 'Fixing of customer ids were right' - ); - } - - /** - * @group customers_v5 - * @expectedException \InvalidArgumentException - */ - public function testCustomersUploadExceptionEmpty() - { - $client = static::getApiClient(); - $client->request->customersUpload([]); - } - - /** - * @group customers_v5 - */ - public function testCustomersUpload() - { - $client = static::getApiClient(); - - $externalIdA = 'upload-a-' . time(); - $externalIdB = 'upload-b-' . time(); - - $response = $client->request->customersUpload([ - [ - 'externalId' => $externalIdA, - 'firstName' => 'Aaa', - ], - [ - 'externalId' => $externalIdB, - 'lastName' => 'Bbb', - ], - ]); - static::assertTrue( - $response->isSuccessful(), - 'Got customer' - ); - static::assertEquals( - $externalIdA, - $response['uploadedCustomers'][0]['externalId'] - ); - static::assertEquals( - $externalIdB, - $response['uploadedCustomers'][1]['externalId'] - ); - } - - /** - * @group customers_v5 - */ - public function testCustomersCombine() - { - $client = static::getApiClient(); - - $responseCreateFirst = $client->request->customersCreate([ - 'firstName' => 'Aaa111', - 'externalId' => 'AA-' . time(), - 'phones' => [ - [ - 'number' => '+79999999990' - ] - ] - ]); - - static::assertTrue( - $responseCreateFirst->isSuccessful(), - 'Got customer' - ); - - $responseCreateSecond = $client->request->customersCreate([ - 'firstName' => 'Aaa222', - 'externalId' => 'BB-' . time(), - 'phones' => [ - [ - 'number' => '+79999999991' - ] - ] - ]); - - static::assertTrue( - $responseCreateSecond->isSuccessful(), - 'Got customer' - ); - - $customers = [ - ['id' => $responseCreateFirst['id']] - ]; - - $resultCustomer = ['id' => $responseCreateSecond['id']]; - - $response = $client->request->customersCombine($customers, $resultCustomer); - - static::assertTrue( - $response->isSuccessful(), - 'Customers combined' - ); - } - - /** - * @group customers_v5 - */ - public function testCustomersNotesCreate() - { - self::markTestSkipped('Should be fixed.'); - $client = static::getApiClient(); - - $responseCreateFirst = $client->request->customersCreate([ - 'firstName' => 'Some', - 'lastName' => 'Test', - 'externalId' => 'AA-' . time(), - 'phones' => [ - [ - 'number' => '+79999999990' - ] - ] - ]); - - static::assertTrue( - $responseCreateFirst->isSuccessful(), - 'Got customer' - ); - - $note = [ - 'managerId' => 6, - 'text' => 'test note', - 'createdAt' => date('Y-m-d H:i:s'), - 'customer' => [ - 'id' => $responseCreateFirst['id'] - ] - ]; - - $response = $client->request->customersNotesCreate($note); - - static::assertTrue($response->isSuccessful(), 'Note created'); - static::assertEquals(201, $response->getStatusCode()); - } - - /** - * @group customers_v5 - */ - public function testCustomersNotesList() - { - $client = static::getApiClient(); - - $responseCreateFirst = $client->request->customersCreate([ - 'firstName' => 'Some', - 'lastName' => 'Test', - 'externalId' => 'AA-' . time(), - 'phones' => [ - [ - 'number' => '+79999999990' - ] - ] - ]); - - static::assertTrue( - $responseCreateFirst->isSuccessful(), - 'Got customer' - ); - - $response = $client->request->customersNotesList(['customerIds' => [$responseCreateFirst['id']]]); - - static::assertTrue($response->isSuccessful(), 'Got notes list'); - static::assertEquals(200, $response->getStatusCode()); - } - - /** - * @group customers_v5 - */ - public function testCustomersNotesDelete() - { - self::markTestSkipped('Should be fixed.'); - - $client = static::getApiClient(); - - $responseCreateFirst = $client->request->customersCreate([ - 'firstName' => 'Some', - 'lastName' => 'Test', - 'externalId' => 'AA-' . time(), - 'phones' => [ - [ - 'number' => '+79999999990' - ] - ] - ]); - - static::assertTrue( - $responseCreateFirst->isSuccessful(), - 'Got customer' - ); - - $note = [ - 'managerId' => 6, - 'text' => 'test note', - 'createdAt' => date('Y-m-d H:i:s'), - 'customer' => [ - 'id' => $responseCreateFirst['id'] - ] - ]; - - $response = $client->request->customersNotesCreate($note); - - static::assertTrue($response->isSuccessful(), 'Note created'); - static::assertEquals(201, $response->getStatusCode()); - - $responseDelete = $client->request->customersNotesDelete($response['id']); - - static::assertTrue($responseDelete->isSuccessful(), 'Note deleted'); - static::assertEquals(200, $responseDelete->getStatusCode()); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version5/ApiClientDeliveryTest.php b/tests/RetailCrm/Tests/Methods/Version5/ApiClientDeliveryTest.php deleted file mode 100644 index 71ac5fe..0000000 --- a/tests/RetailCrm/Tests/Methods/Version5/ApiClientDeliveryTest.php +++ /dev/null @@ -1,106 +0,0 @@ -request->deliveryShipmentsList(); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals($response->getStatusCode(), 200); - static::assertTrue($response->isSuccessful()); - } - - - /** - * Test delivery methods - * - * @group marketplace_v5 - * - * @return void - */ - public function testDeliveryShipments() - { - self::markTestSkipped('Should be fixed.'); - - $client = static::getApiClient(); - - $deliveryType = 'courier'; - - $order = [ - 'number' => uniqid(), - 'firstName' => 'Test', - 'lastName' => 'Customer', - 'email' => 'test@example.com', - 'delivery' => ['code' => $deliveryType] - ]; - - $responseOrder = $client->request->ordersCreate($order); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $responseOrder); - static::assertEquals($responseOrder->getStatusCode(), 201); - static::assertTrue($responseOrder->isSuccessful()); - - $orderid = $responseOrder['id']; - - $shipment = [ - 'date' => date('Y-m-d'), - 'orders' => [ - [ - 'id' => $orderid - ] - ], - 'comment' => 'test shipment' - ]; - - $responseCreate = $client - ->request - ->deliveryShipmentsCreate($shipment, $deliveryType); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $responseCreate); - static::assertTrue($responseCreate->isSuccessful()); - - $responseGet = $client->request->deliveryShipmentGet($responseCreate['id']); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $responseGet); - static::assertTrue($responseGet->isSuccessful()); - - $updateShipment = array_merge($shipment, ['status' => 'cancelled']); - - /* @var \RetailCrm\Response\ApiResponse $responseUpdate */ - $responseUpdate = $client - ->request - ->deliveryShipmentsUpdate($updateShipment); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $responseUpdate); - static::assertTrue($responseUpdate->isSuccessful()); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version5/ApiClientFilesTest.php b/tests/RetailCrm/Tests/Methods/Version5/ApiClientFilesTest.php deleted file mode 100644 index f2a268b..0000000 --- a/tests/RetailCrm/Tests/Methods/Version5/ApiClientFilesTest.php +++ /dev/null @@ -1,87 +0,0 @@ -request->filesList(); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group files_v5 - */ - public function testFileUpload() - { - $client = static::getApiClient(); - - $response = $client->request->fileUpload(__DIR__ . '/../../../Tests/Resources/Report.pdf'); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - - sleep(1); - - $fileId = $response['file']['id']; - - $response = $client->request->fileGet($fileId); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - - sleep(1); - - $response = $client->request->fileEdit($fileId, ['filename' => 'Test file']); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - - sleep(1); - - $response = $client->request->fileDelete($fileId); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - - sleep(1); - } - - public function testFileEditFailure() - { - static::expectExceptionObject(new \InvalidArgumentException('Invalid structure of `file` parameter')); - $client = static::getApiClient(); - - $client->request->fileEdit(1, ['file' => []]); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version5/ApiClientMarketplaceTest.php b/tests/RetailCrm/Tests/Methods/Version5/ApiClientMarketplaceTest.php deleted file mode 100644 index ecf6155..0000000 --- a/tests/RetailCrm/Tests/Methods/Version5/ApiClientMarketplaceTest.php +++ /dev/null @@ -1,57 +0,0 @@ -request->integrationModulesEdit( - [ - 'name' => self::SERVICE_NAME, - 'code' => self::SERVICE_CODE, - 'clientId' => uniqid(), - 'logo' => 'http://download.retailcrm.pro/logos/setup.svg', - 'active' => 'true' - ] - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals($response->getStatusCode(), 200); - static::assertTrue($response->isSuccessful()); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version5/ApiClientOrdersTest.php b/tests/RetailCrm/Tests/Methods/Version5/ApiClientOrdersTest.php deleted file mode 100644 index b9398b8..0000000 --- a/tests/RetailCrm/Tests/Methods/Version5/ApiClientOrdersTest.php +++ /dev/null @@ -1,420 +0,0 @@ -request->ordersCreate([ - 'firstName' => self::FIRST_NAME, - 'externalId' => $externalId, - ]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(201, $response->getStatusCode()); - static::assertTrue(is_int($response['id'])); - - return [ - 'id' => $response['id'], - 'externalId' => $externalId, - ]; - } - - /** - * @group orders_v5 - * @expectedException \InvalidArgumentException - */ - public function testOrdersCreateExceptionEmpty() - { - $client = static::getApiClient(); - $client->request->ordersCreate([]); - } - - /** - * @group orders - * @depends testOrdersCreate - * - * @param array $ids - */ - public function testOrdersStatuses(array $ids) - { - $client = static::getApiClient(); - - $response = $client->request->ordersStatuses(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(400, $response->getStatusCode()); - static::assertFalse($response->isSuccessful()); - - $response = $client->request->ordersStatuses([], ['asdf']); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - $orders = $response['orders']; - static::assertEquals(0, sizeof($orders)); - - $response = $client->request->ordersStatuses([], [$ids['externalId']]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - $orders = $response['orders']; - static::assertEquals(1, sizeof($orders)); - static::assertEquals('new', $orders[0]['status']); - - $response = $client->request->ordersStatuses([$ids['id']], [$ids['externalId']]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - $orders = $response['orders']; - static::assertEquals(1, sizeof($orders)); - - $response = $client->request->ordersStatuses([$ids['id']]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - $orders = $response['orders']; - static::assertEquals(1, sizeof($orders)); - } - - /** - * @group orders - * @depends testOrdersCreate - * - * @param array $ids - * - * @return array - */ - public function testOrdersGet(array $ids) - { - $client = static::getApiClient(); - - $response = $client->request->ordersGet(678678678); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(404, $response->getStatusCode()); - static::assertFalse($response->isSuccessful()); - - $response = $client->request->ordersGet($ids['id'], 'id'); - $orderById = $response['order']; - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - static::assertEquals(self::FIRST_NAME, $response['order']['firstName']); - - $response = $client->request->ordersGet($ids['externalId'], 'externalId'); - static::assertEquals($orderById['id'], $response['order']['id']); - - return $ids; - } - - /** - * @group orders_v5 - * @expectedException \InvalidArgumentException - */ - public function testOrdersGetException() - { - $client = static::getApiClient(); - $client->request->ordersGet(678678678, 'asdf'); - } - - /** - * @group orders - * @depends testOrdersGet - * - * @param array $ids - */ - public function testOrdersEdit(array $ids) - { - $client = static::getApiClient(); - - $response = $client->request->ordersEdit( - [ - 'id' => 22342134, - 'lastName' => '12345', - ], - 'id' - ); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(404, $response->getStatusCode()); - - $response = $client->request->ordersEdit([ - 'externalId' => $ids['externalId'], - 'lastName' => '12345', - ]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group orders_v5 - * @expectedException \InvalidArgumentException - */ - public function testOrdersEditExceptionEmpty() - { - $client = static::getApiClient(); - $client->request->ordersEdit([], 'asdf'); - } - - /** - * @group orders_v5 - * @expectedException \InvalidArgumentException - */ - public function testOrdersEditException() - { - $client = static::getApiClient(); - $client->request->ordersEdit(['id' => 678678678], 'asdf'); - } - - /** - * @group orders_v5 - */ - public function testOrdersHistory() - { - $client = static::getApiClient(); - - $response = $client->request->ordersHistory(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group orders_v5 - */ - public function testOrdersList() - { - $client = static::getApiClient(); - - $response = $client->request->ordersList(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful()); - static::assertTrue(isset($response['orders'])); - - $response = $client->request->ordersList([], 1, 300); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertFalse( - $response->isSuccessful(), - 'Pagination error' - ); - - $response = $client->request->ordersList(['paymentStatus' => 'paid'], 1); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - } - - /** - * @group orders_v5 - * @expectedException \InvalidArgumentException - */ - public function testOrdersFixExternalIdsException() - { - $client = static::getApiClient(); - $client->request->ordersFixExternalIds([]); - } - - /** - * @group orders_v5 - */ - public function testOrdersFixExternalIds() - { - $client = static::getApiClient(); - - $response = $client->request->ordersCreate([ - 'firstName' => 'Aaa', - ]); - static::assertTrue( - $response->isSuccessful(), - 'Order created' - ); - - $id = $response['id']; - $externalId = 'asdf' . time(); - - $response = $client->request->ordersFixExternalIds([ - ['id' => $id, 'externalId' => $externalId] - ]); - - static::assertTrue( - $response->isSuccessful(), - 'Fixed order ids' - ); - - $response = $client->request->ordersGet($externalId); - static::assertTrue( - $response->isSuccessful(), - 'Got order' - ); - static::assertEquals( - $id, - $response['order']['id'], - 'Fixing of order ids were right' - ); - static::assertEquals( - $externalId, - $response['order']['externalId'], - 'Fixing of order ids were right' - ); - } - - /** - * @group orders_v5 - * @expectedException \InvalidArgumentException - */ - public function testOrdersUploadExceptionEmpty() - { - $client = static::getApiClient(); - $client->request->ordersUpload([]); - } - - /** - * @group orders_v5 - */ - public function testOrdersUpload() - { - $client = static::getApiClient(); - - $externalIdA = 'upload-a-' . time(); - $externalIdB = 'upload-b-' . time(); - - $response = $client->request->ordersUpload([ - [ - 'externalId' => $externalIdA, - 'firstName' => 'Aaa', - ], - [ - 'externalId' => $externalIdB, - 'lastName' => 'Bbb', - ], - ]); - static::assertTrue( - $response->isSuccessful(), - 'Got order' - ); - static::assertEquals( - $externalIdA, - $response['uploadedOrders'][0]['externalId'] - ); - static::assertEquals( - $externalIdB, - $response['uploadedOrders'][1]['externalId'] - ); - } - - /** - * @group orders_v5 - */ - public function testOrdersCombine() - { - self::markTestSkipped('Should be fixed.'); - $client = static::getApiClient(); - - $responseCreateFirst = $client->request->ordersCreate([ - 'firstName' => 'Aaa111', - 'externalId' => 'AA-' . time(), - 'phone' => '+79999999990' - ]); - - static::assertTrue( - $responseCreateFirst->isSuccessful(), - 'Got order' - ); - - $responseCreateSecond = $client->request->ordersCreate([ - 'firstName' => 'Aaa222', - 'externalId' => 'BB-' . time(), - 'phone' => '+79999999991' - ]); - - static::assertTrue( - $responseCreateSecond->isSuccessful(), - 'Got order' - ); - - $order = ['id' => $responseCreateFirst['id']]; - $resultOrder = ['id' => $responseCreateSecond['id']]; - - $response = $client->request->ordersCombine($order, $resultOrder, 'ours'); - - static::assertTrue( - $response->isSuccessful(), - 'Orders combined' - ); - } - - public function testOrdersPayment() - { - $client = static::getApiClient(); - $externalId = 'AA-' . time(); - - $responseCreateFirst = $client->request->ordersCreate([ - 'firstName' => 'Aaa111aaA', - 'phone' => '+79999999990' - ]); - - static::assertTrue( - $responseCreateFirst->isSuccessful(), - 'Got order' - ); - - $payment = [ - 'externalId' => $externalId, - 'order' => ['id' => $responseCreateFirst['id']], - 'amount' => 1200, - 'comment' => 'test payment', - 'type' => 'cash', - 'status' => 'paid' - ]; - - $response = $client->request->ordersPaymentCreate($payment); - - static::assertTrue( - $response->isSuccessful(), - 'Got payment' - ); - - $paymentEdit = [ - 'id' => $response['id'], - 'amount' => 1500, - 'comment' => 'test payment!', - 'status' => 'paid' - ]; - - $responseAgain = $client->request->ordersPaymentEdit($paymentEdit); - - static::assertTrue( - $responseAgain->isSuccessful(), - 'Edit payment' - ); - - $responseLast = $client->request->ordersPaymentDelete($response['id']); - - static::assertTrue( - $responseLast->isSuccessful(), - 'Delete payment' - ); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version5/ApiClientPacksTest.php b/tests/RetailCrm/Tests/Methods/Version5/ApiClientPacksTest.php deleted file mode 100644 index 1c7e977..0000000 --- a/tests/RetailCrm/Tests/Methods/Version5/ApiClientPacksTest.php +++ /dev/null @@ -1,63 +0,0 @@ -request->ordersPacksHistory(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->success); - static::assertTrue( - isset($response['history']), - 'API returns orders assembly history' - ); - static::assertTrue( - isset($response['generatedAt']), - 'API returns generatedAt in orders assembly history' - ); - } - - /** - * @group packs_v5 - */ - public function testPacksCreateFailed() - { - $client = static::getApiClient(); - - $pack = [ - 'itemId' => 12, - 'store' => 'test', - 'quantity' => 2 - ]; - - $response = $client->request->ordersPacksCreate($pack); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(400, $response->getStatusCode()); - static::assertFalse($response->success); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version5/ApiClientPricesTest.php b/tests/RetailCrm/Tests/Methods/Version5/ApiClientPricesTest.php deleted file mode 100644 index a888a97..0000000 --- a/tests/RetailCrm/Tests/Methods/Version5/ApiClientPricesTest.php +++ /dev/null @@ -1,107 +0,0 @@ -request->pricesTypesEdit( - [ - 'code' => 'sample_v4_price_code', - 'name' => 'Sample v4 price type', - 'ordering' => 500, - 'active' => true - ] - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * @depends testPricesEdit - * @group prices_v5 - */ - public function testPricesGet() - { - $client = static::getApiClient(); - $response = $client->request->pricesTypes(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - static::assertTrue($response->isSuccessful()); - } - - - - /** - * @group prices_v5 - * @expectedException \InvalidArgumentException - */ - public function testPricesUploadExceptionEmpty() - { - $client = static::getApiClient(); - $client->request->storePricesUpload([]); - } - - /** - * @depends testPricesEdit - * @group prices_v5 - */ - public function testPricesUpload() - { - - $client = static::getApiClient(); - - $xmlIdA = 'upload-a-' . time(); - $xmlIdB = 'upload-b-' . time(); - - $response = $client->request->storePricesUpload([ - [ - 'xmlId' => $xmlIdA, - 'prices' => [ - [ - 'code' => 'sample_v4_price_code', - 'price' => 1700 - ] - ] - ], - [ - 'xmlId' => $xmlIdB, - 'prices' => [ - [ - 'code' => 'sample_v4_price_code', - 'price' => 1500 - ] - ] - ], - ]); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version5/ApiClientReferenceTest.php b/tests/RetailCrm/Tests/Methods/Version5/ApiClientReferenceTest.php deleted file mode 100644 index 1d50196..0000000 --- a/tests/RetailCrm/Tests/Methods/Version5/ApiClientReferenceTest.php +++ /dev/null @@ -1,207 +0,0 @@ -request->$method(); - - /* @var \RetailCrm\Response\ApiResponse $response */ - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful()); - static::assertTrue(isset($response[$name])); - static::assertTrue(is_array($response[$name])); - } - - /** - * @group reference_v5 - * @dataProvider getEditDictionaries - * @expectedException \InvalidArgumentException - * - * @param $name - */ - public function testEditingException($name) - { - - $client = static::getApiClient(); - - $method = $name . 'Edit'; - $client->request->$method([]); - } - - /** - * @group reference_v5 - * @dataProvider getEditDictionaries - * - * @param $name - */ - public function testEditing($name) - { - - $client = static::getApiClient(); - - $code = 'dict-' . strtolower($name) . '-' . time(); - $method = $name . 'Edit'; - $params = [ - 'code' => $code, - 'name' => 'Aaa' . $code, - 'active' => false - ]; - if ($name == 'statuses') { - $params['group'] = 'new'; - } - - $response = $client->request->$method($params); - /* @var \RetailCrm\Response\ApiResponse $response */ - - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - - $response = $client->request->$method([ - 'code' => $code, - 'name' => 'Bbb' . $code, - 'active' => false - ]); - - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - } - - /** - * @group reference_v5 - * @group site - */ - public function testSiteEditing() - { - $name = 'sites'; - - $client = static::getApiClient(); - - $code = 'dict-' . strtolower($name) . '-' . time(); - $method = $name . 'Edit'; - $params = [ - 'code' => $code, - 'name' => 'Aaa', - 'active' => false - ]; - - $response = $client->request->$method($params); - /* @var \RetailCrm\Response\ApiResponse $response */ - - static::assertEquals(400, $response->getStatusCode()); - - if ($code == $client->request->getSite()) { - $method = $name . 'Edit'; - $params = [ - 'code' => $code, - 'name' => 'Aaa' . time(), - 'active' => false - ]; - - $response = $client->request->$method($params); - static::assertEquals(200, $response->getStatusCode()); - } - } - - /** - * @group reference_v5 - */ - public function testUnitsEditing() - { - $client = static::getApiClient(); - - $unit = [ - 'code' => 'test', - 'name' => 'Test', - 'sym' => 'tst' - ]; - - $response = $client->request->unitsEdit($unit); - - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - } - - /** - * @group reference_v5 - * @expectedException \InvalidArgumentException - */ - public function testUnitsEditingFail() - { - $client = static::getApiClient(); - - $unit = [ - 'name' => 'Test', - 'sym' => 'tst' - ]; - - $client->request->unitsEdit($unit); - } - - /** - * @return array - */ - public function getListDictionaries() - { - return [ - ['deliveryServices'], - ['deliveryTypes'], - ['orderMethods'], - ['orderTypes'], - ['paymentStatuses'], - ['paymentTypes'], - ['productStatuses'], - ['statusGroups'], - ['statuses'], - ['sites'], - ['stores'], - ['couriers'], - ['costs'], - ['units'] - ]; - } - - /** - * @return array - */ - public function getEditDictionaries() - { - return [ - ['deliveryServices'], - ['deliveryTypes'], - ['orderMethods'], - ['orderTypes'], - ['paymentStatuses'], - ['paymentTypes'], - ['productStatuses'], - ['statuses'], - ]; - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version5/ApiClientStoreTest.php b/tests/RetailCrm/Tests/Methods/Version5/ApiClientStoreTest.php deleted file mode 100644 index 59aa215..0000000 --- a/tests/RetailCrm/Tests/Methods/Version5/ApiClientStoreTest.php +++ /dev/null @@ -1,170 +0,0 @@ -request->storesEdit(['name' => self::SNAME, 'code' => self::SCODE]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group store_v5 - */ - public function testStoreInventories() - { - - $client = static::getApiClient(); - - $response = $client->request->storeInventories(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - static::assertTrue( - isset($response['offers']), - 'API returns orders assembly history' - ); - } - - /** - * @group store_v5 - * @expectedException \InvalidArgumentException - */ - public function testInventoriesException() - { - - $client = static::getApiClient(); - $client->request->storeInventoriesUpload([]); - } - - /** - * @group store_v5 - */ - public function testInventoriesUpload() - { - - $client = static::getApiClient(); - - $response = $client->request->storeInventoriesUpload([ - [ - 'externalId' => 'pTKIKAeghYzX21HTdzFCe1', - 'stores' => [ - [ - 'code' => self::SCODE, - 'available' => 10, - 'purchasePrice' => 1700 - ] - ] - ], - [ - 'externalId' => 'JQIvcrCtiSpOV3AAfMiQB3', - 'stores' => [ - [ - 'code' => self::SCODE, - 'available' => 20, - 'purchasePrice' => 1500 - ] - ] - ], - ]); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group store_v5 - */ - public function testInventoriesFailed() - { - - $client = static::getApiClient(); - - $externalIdA = 'upload-a-' . time(); - $externalIdB = 'upload-b-' . time(); - - $response = $client->request->storeInventoriesUpload([ - [ - 'externalId' => $externalIdA, - 'available' => 10, - 'purchasePrice' => 1700 - ], - [ - 'externalId' => $externalIdB, - 'available' => 20, - 'purchasePrice' => 1500 - ], - ]); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(400, $response->getStatusCode()); - static::assertTrue(isset($response['errorMsg']), $response['errorMsg']); - } - - /** - * @group store_v5 - */ - public function testStoreProducts() - { - - $client = static::getApiClient(); - - $response = $client->request->storeProducts(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group store_v5 - */ - public function testStoreProductsGroups() - { - - $client = static::getApiClient(); - - $response = $client->request->storeProductsGroups(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group store_v5 - * @expectedException \InvalidArgumentException - */ - public function testStoreSettingsGet() - { - $client = static::getApiClient(); - $client->request->storeSettingsGet(self::SCODE); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version5/ApiClientTasksTest.php b/tests/RetailCrm/Tests/Methods/Version5/ApiClientTasksTest.php deleted file mode 100644 index fe51794..0000000 --- a/tests/RetailCrm/Tests/Methods/Version5/ApiClientTasksTest.php +++ /dev/null @@ -1,78 +0,0 @@ -request->tasksList(); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - } - - /** - * @group tasks_v5 - * @expectedException \InvalidArgumentException - */ - public function testTasksCreateExceptionEmpty() - { - $client = static::getApiClient(); - $client->request->tasksCreate([]); - } - - public function testTasksCRU() - { - $client = static::getApiClient(); - $user = getenv('RETAILCRM_USER') ?: $_SERVER['RETAILCRM_USER']; - $task = [ - 'text' => 'test task', - 'commentary' => 'test task commentary', - 'performerId' => $user, - 'complete' => false - ]; - - $responseCreate = $client->request->tasksCreate($task); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $responseCreate); - static::assertEquals(201, $responseCreate->getStatusCode()); - - $uid = $responseCreate['id']; - - $responseRead = $client->request->tasksGet($uid); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $responseRead); - static::assertEquals(200, $responseRead->getStatusCode()); - - $task['id'] = $uid; - $task['complete'] = true; - - $responseUpdate = $client->request->tasksEdit($task); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $responseUpdate); - static::assertEquals(200, $responseUpdate->getStatusCode()); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version5/ApiClientTelephonyTest.php b/tests/RetailCrm/Tests/Methods/Version5/ApiClientTelephonyTest.php deleted file mode 100644 index 54de8d5..0000000 --- a/tests/RetailCrm/Tests/Methods/Version5/ApiClientTelephonyTest.php +++ /dev/null @@ -1,118 +0,0 @@ -request->telephonyCallEvent( - '+79999999999', - 'in', - ['101'], - 'failed', - '+74950000000' - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * Upload test - * - * @group telephony - * - * @return void - */ - public function testTelephonyUpload() - { - self::markTestSkipped('Should be fixed.'); - $client = static::getApiClient(); - $response = $client->request->telephonyCallsUpload( - [ - [ - 'date' => '2016-07-22 00:18:00', - 'type' => 'in', - 'phone' => '+79999999999', - 'code' => '101', - 'result' => 'answered', - 'externalId' => rand(10, 100), - 'recordUrl' => 'http://download.retailcrm.pro/api-client-files/beep1.mp3' - ], - [ - 'date' => '2016-07-22 00:24:00', - 'type' => 'in', - 'phone' => '+79999999999', - 'code' => '101', - 'result' => 'answered', - 'externalId' => rand(10, 100), - 'recordUrl' => 'http://download.retailcrm.pro/api-client-files/beep2.mp3' - ] - ] - ); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * Manager test - * - * @group telephony - * - * @return void - */ - public function testTelephonyManager() - { - self::markTestSkipped('Should be fixed.'); - $client = static::getApiClient(); - $response = $client->request->telephonyCallManager('+79999999999', 1); - - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group telephony_v5 - * @expectedException \InvalidArgumentException - */ - public function testTelephonySettingsGet() - { - $client = static::getApiClient(); - $client->request->telephonySettingsGet(self::TEL_CODE); - } -} diff --git a/tests/RetailCrm/Tests/Methods/Version5/ApiClientUsersTest.php b/tests/RetailCrm/Tests/Methods/Version5/ApiClientUsersTest.php deleted file mode 100644 index 8272932..0000000 --- a/tests/RetailCrm/Tests/Methods/Version5/ApiClientUsersTest.php +++ /dev/null @@ -1,75 +0,0 @@ -request->usersGroups(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group users_v5 - */ - public function testUsersList() - { - $client = static::getApiClient(); - - $response = $client->request->usersList(); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group users_v5 - */ - public function testUsersGet() - { - $client = static::getApiClient(); - $user = getenv('RETAILCRM_USER') ?: $_SERVER['RETAILCRM_USER']; - $response = $client->request->usersGet($user); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - static::assertTrue($response->isSuccessful()); - } - - /** - * @group users_v5 - */ - public function testUsersStatus() - { - $client = static::getApiClient(); - $user = getenv('RETAILCRM_USER') ?: $_SERVER['RETAILCRM_USER']; - $response = $client->request->usersStatus($user, 'dinner'); - static::assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - static::assertTrue(in_array($response->getStatusCode(), [200, 201])); - static::assertTrue($response->isSuccessful()); - } -} diff --git a/tests/RetailCrm/Tests/Resources/Report.pdf b/tests/RetailCrm/Tests/Resources/Report.pdf deleted file mode 100644 index 0217da5d11f8c7676f751ef67c8b4d69fa4c04d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124225 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
ii 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 D&w=1Z diff --git a/tests/RetailCrm/Tests/Response/ApiResponseTest.php b/tests/RetailCrm/Tests/Response/ApiResponseTest.php deleted file mode 100644 index 24a93f4..0000000 --- a/tests/RetailCrm/Tests/Response/ApiResponseTest.php +++ /dev/null @@ -1,283 +0,0 @@ -asJsonResponse(); - static::assertInstanceOf( - 'RetailCrm\Response\ApiResponse', - $response, - 'Response object created' - ); - } - - /** - * @group response - * @expectedException \RetailCrm\Exception\InvalidJsonException - */ - public function testJsonInvalid() - { - (new ApiResponse(400, '{ "asdf": }'))->asJsonResponse(); - } - - /** - * @group response - */ - public function testJsonInvalidNoDeserialize() - { - $response = new ApiResponse(400, '{ "asdf": }'); - static::assertInstanceOf( - 'RetailCrm\Response\ApiResponse', - $response, - 'Response object created' - ); - } - - /** - * @group response - */ - public function testStatusCodeGetting() - { - $response = new ApiResponse(200); - static::assertEquals( - 200, - $response->getStatusCode(), - 'Response object returns the right status code' - ); - - $response = (new ApiResponse(460, '{ "success": false }'))->asJsonResponse(); - static::assertEquals( - 460, - $response->getStatusCode(), - 'Response object returns the right status code' - ); - } - - /** - * @group response - */ - public function testIsSuccessful() - { - $response = new ApiResponse(200); - static::assertTrue( - $response->isSuccessful(), - 'Request was successful' - ); - - $response = (new ApiResponse(460, '{ "success": false }'))->asJsonResponse(); - static::assertFalse( - $response->isSuccessful(), - 'Request was failed' - ); - } - - /** - * @group response - */ - public function testMagicCall() - { - $response = (new ApiResponse(201, '{ "success": true }'))->asJsonResponse(); - static::assertEquals( - true, - $response->isSuccessful(), - 'Response object returns property value throw magic method' - ); - } - - /** - * @group response - * @expectedException \InvalidArgumentException - */ - public function testMagicCallException1() - { - $response = new ApiResponse(200); - /* @noinspection PhpUndefinedMethodInspection */ - $response->getSome(); - } - - /** - * @group response - * @expectedException \InvalidArgumentException - */ - public function testMagicCallException2() - { - $response = (new ApiResponse(201, '{ "success": true }'))->asJsonResponse(); - /* @noinspection PhpUndefinedMethodInspection */ - $response->getSomeSuccess(); - } - - /** - * @group response - */ - public function testMagicGet() - { - $response = (new ApiResponse(201, '{ "success": true }'))->asJsonResponse(); - static::assertEquals( - true, - $response->success, - 'Response object returns property value throw magic get' - ); - } - - /** - * @group response - * @expectedException \InvalidArgumentException - */ - public function testMagicGetException1() - { - $response = new ApiResponse(200); - /* @noinspection PhpUndefinedFieldInspection */ - $response->some; - } - - /** - * @group response - * @expectedException \InvalidArgumentException - */ - public function testMagicGetException2() - { - $response = (new ApiResponse(201, '{ "success": true }'))->asJsonResponse(); - /* @noinspection PhpUndefinedFieldInspection */ - $response->someSuccess; - } - - /** - * @group response - */ - public function testArrayGet() - { - $response = (new ApiResponse(201, '{ "success": true }'))->asJsonResponse(); - static::assertEquals( - true, - $response['success'], - 'Response object returns property value throw magic array get' - ); - } - - /** - * @group response - * @expectedException \InvalidArgumentException - */ - public function testArrayGetException1() - { - $response = new ApiResponse(200); - $response['some']; - } - - /** - * @group response - * @expectedException \InvalidArgumentException - */ - public function testArrayGetException2() - { - $response = (new ApiResponse(201, '{ "success": true }'))->asJsonResponse(); - $response['someSuccess']; - } - - /** - * @group response - */ - public function testArrayIsset() - { - $response = (new ApiResponse(201, '{ "success": true }'))->asJsonResponse(); - - static::assertTrue( - isset($response['success']), - 'Response object returns property existing' - ); - - static::assertFalse( - isset($response['suess']), - 'Response object returns property existing' - ); - } - - /** - * @group response - * @expectedException \BadMethodCallException - */ - public function testArraySetException1() - { - $response = (new ApiResponse(201, '{ "success": true }'))->asJsonResponse(); - $response['success'] = 'a'; - } - - /** - * @group response - * @expectedException \BadMethodCallException - */ - public function testArraySetException2() - { - $response = (new ApiResponse(201, '{ "success": true }'))->asJsonResponse(); - $response['sssssssuccess'] = 'a'; - } - - /** - * @group response - * @expectedException \BadMethodCallException - */ - public function testArrayUnsetException1() - { - $response = (new ApiResponse(201, '{ "success": true }'))->asJsonResponse(); - unset($response['success']); - } - - /** - * @group response - * @expectedException \BadMethodCallException - */ - public function testArrayUnsetException2() - { - $response = (new ApiResponse(201, '{ "success": true }'))->asJsonResponse(); - unset($response['sssssssuccess']); - } - - /** - * @group response - */ - public function testMagicIsset() - { - $response = (new ApiResponse(201, '{ "success": true }'))->asJsonResponse(); - - static::assertTrue( - isset($response->success), - 'Response object returns property existing' - ); - - static::assertFalse( - isset($response->suess), - 'Response object returns property existing' - ); - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 26ce5dc..771a104 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,10 +1,26 @@ add('RetailCrm\\Test', __DIR__); +if (!is_file($autoloadFile = __DIR__ . '/../vendor/autoload.php')) { + throw new RuntimeException('Did not find vendor/autoload.php. Did you run "composer install --dev"?'); +} + +$loader = require $autoloadFile; +$loader->add('RetailCrm\\TestUtils', __DIR__ . '/tests/utils'); +$loader->add('RetailCrm\\Tests', __DIR__ . '/src'); +AnnotationRegistry::registerLoader('class_exists'); + +if (file_exists(__DIR__ . '/../.env')) { + $dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..'); + $dotenv->load(); +} diff --git a/tests/src/Builder/ClientBuilderTest.php b/tests/src/Builder/ClientBuilderTest.php new file mode 100644 index 0000000..05770ef --- /dev/null +++ b/tests/src/Builder/ClientBuilderTest.php @@ -0,0 +1,96 @@ +expectException(BuilderException::class); + $this->expectDeprecationMessage('apiUrl must not be empty'); + (new ClientBuilder())->build(); + } + + public function testNoDataCheckFields(): void + { + try { + (new ClientBuilder())->build(); + } catch (BuilderException $exception) { + self::assertEquals(['apiUrl'], $exception->getInvalidArgument()); + } + } + + public function testNoRequestTransformer(): void + { + $this->expectException(BuilderException::class); + $this->expectDeprecationMessage('Authenticator or RequestTransformer must be present'); + (new ClientBuilder()) + ->setApiUrl('https://test.retailcrm.pro') + ->build(); + } + + public function testBuild(): void + { + $eventDispatcher = new EventDispatcher(); + $psr17Factory = new Psr17Factory(); + $httpClient = new CurlClient(); + + $builder = new ClientBuilder(); + $formEncoder = (new FormEncoderBuilder())->setCacheDir('cache')->build(); + $requestTransformer = new RequestTransformer(RequestPipelineFactory::createDefaultPipeline( + $formEncoder, + $psr17Factory, // PSR-17 UriFactoryInterface + $psr17Factory, // PSR-17 RequestFactoryInterface + $psr17Factory // PSR-17 StreamFactoryInterface + )); + $responseTransformer = new ResponseTransformer(ResponsePipelineFactory::createDefaultPipeline( + $formEncoder->getSerializer(), + new ApiExceptionFactory(), + $eventDispatcher + )); + $client = $builder->setApiUrl(TestConfig::getApiUrl()) + ->setAuthenticatorHandler(new HeaderAuthenticatorHandler(TestConfig::getApiKey())) + ->setFormEncoder($formEncoder) + ->setHttpClient($httpClient) + ->setRequestTransformer($requestTransformer) + ->setResponseTransformer($responseTransformer) + ->build(); + + /** @var \RetailCrm\Api\ResourceGroup\Api $api */ + $api = ReflectionUtils::getProperty($client, 'api'); + + self::assertStringContainsString(TestConfig::getApiUrl(), ReflectionUtils::getProperty($api, 'baseUrl')); + self::assertEquals($httpClient, ReflectionUtils::getProperty($api, 'httpClient')); + self::assertEquals($requestTransformer, ReflectionUtils::getProperty($api, 'requestTransformer')); + self::assertEquals($responseTransformer, ReflectionUtils::getProperty($api, 'responseTransformer')); + } +} diff --git a/tests/src/ClientTest.php b/tests/src/ClientTest.php new file mode 100644 index 0000000..9b513a6 --- /dev/null +++ b/tests/src/ClientTest.php @@ -0,0 +1,124 @@ +site = 'moysklad'; + $request->note = new CustomerNote(); + $request->note->customer = new Customer(); + $request->note->customer->externalId = '10'; + $request->note->managerId = 21; + $request->note->text = 'Text'; + + $encoded = $formEncoder->encode($request); + self::assertNotEmpty($encoded); + + $annotationReader = ReflectionUtils::getProperty($formEncoder, 'annotationReader'); + self::assertInstanceOf(PsrCachedReader::class, $annotationReader); + + $cache = ReflectionUtils::getProperty($annotationReader, 'cache'); + $cacheDir = ReflectionUtils::getProperty($cache, 'directory'); + + self::assertInstanceOf(FilesystemAdapter::class, $cache); + self::assertStringStartsWith($dir, $cacheDir); + self::assertDirectoryExists($cacheDir); + self::assertTrue(is_readable($cacheDir) && count(scandir($cacheDir)) > 2); + + static::recursiveRmdir($dir); + } + + /** + * @param \RetailCrm\Api\Interfaces\HandlerInterface $handler + * + * @return \RetailCrm\Api\Handler\Request\RequestDataHandler + */ + private static function findModelDataHandler(HandlerInterface $handler): RequestDataHandler + { + if ($handler instanceof RequestDataHandler) { + return $handler; + } + + while ($handler = $handler->getNext()) { + if ($handler instanceof RequestDataHandler) { + return $handler; + } + } + + throw new RuntimeException('RequestDataHandler is not present in the chain.'); + } + + /** + * @param string $dir + */ + private static function recursiveRmdir(string $dir): void + { + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($files as $fileInfo) { + $todo = ($fileInfo->isDir() ? 'rmdir' : 'unlink'); + $todo($fileInfo->getRealPath()); + } + + rmdir($dir); + } +} diff --git a/tests/src/Command/ClearModelsCommandTest.php b/tests/src/Command/ClearModelsCommandTest.php new file mode 100644 index 0000000..d979e1c --- /dev/null +++ b/tests/src/Command/ClearModelsCommandTest.php @@ -0,0 +1,32 @@ +execute([]); + + self::assertStringContainsString('Cleaning up', $tester->getDisplay()); + self::assertStringContainsString('Done!', $tester->getDisplay()); + } +} diff --git a/tests/src/Command/CompilerPromptCommandTest.php b/tests/src/Command/CompilerPromptCommandTest.php new file mode 100644 index 0000000..d2f649a --- /dev/null +++ b/tests/src/Command/CompilerPromptCommandTest.php @@ -0,0 +1,70 @@ +execute([]); + + self::assertStringContainsString('Done, generator prompt is now disabled', $tester->getDisplay()); + + $composerJson = static::getComposerJson(); + + self::assertArrayHasKey('extra', $composerJson); + self::assertArrayHasKey('compile-mode', $composerJson['extra']); + self::assertArrayHasKey('compile-whitelist', $composerJson['extra']); + self::assertEquals('whitelist', $composerJson['extra']['compile-mode']); + self::assertEquals(['retailcrm/api-client-php'], $composerJson['extra']['compile-whitelist']); + } + + public function testActivate(): void + { + $tester = new CommandTester(new CompilerPromptCommand()); + $tester->execute(['--activate' => '']); + + self::assertStringContainsString('Done, generator prompt is now enabled', $tester->getDisplay()); + + $composerJson = static::getComposerJson(); + + self::assertArrayHasKey('extra', $composerJson); + self::assertArrayNotHasKey('compile-mode', $composerJson['extra']); + self::assertArrayNotHasKey('compile-whitelist', $composerJson['extra']); + } + + /** + * @return array + * @throws \JsonException + */ + private static function getComposerJson(): array + { + $composerJson = ComposerLocator::findComposerJson(); + + if ('' === $composerJson) { + throw new RuntimeException('composer.json not found'); + } + + return json_decode((string) file_get_contents($composerJson), true, 512, JSON_THROW_ON_ERROR); + } +} diff --git a/tests/src/Command/GenerateModelsCommandTest.php b/tests/src/Command/GenerateModelsCommandTest.php new file mode 100644 index 0000000..b815270 --- /dev/null +++ b/tests/src/Command/GenerateModelsCommandTest.php @@ -0,0 +1,31 @@ +execute([]); + + self::assertStringContainsString('Done, generated code for', $tester->getDisplay()); + } +} diff --git a/tests/src/Command/VerifyModelsCommandTest.php b/tests/src/Command/VerifyModelsCommandTest.php new file mode 100644 index 0000000..dbf2e67 --- /dev/null +++ b/tests/src/Command/VerifyModelsCommandTest.php @@ -0,0 +1,31 @@ +execute([]); + + self::assertStringContainsString('Models are up to date.', $tester->getDisplay()); + } +} diff --git a/tests/src/Component/ComposerLocatorTest.php b/tests/src/Component/ComposerLocatorTest.php new file mode 100644 index 0000000..9152de1 --- /dev/null +++ b/tests/src/Component/ComposerLocatorTest.php @@ -0,0 +1,38 @@ +setFileNameAccessor(static function (array $item) { + return $item['file']; + })->setKeyTransformer(static function (array $item) { + return $item['fqn']; + })->generateHashes(); + + $iterator->rewind(); + + foreach ($iterator as $item) { + self::assertArrayHasKey($item['fqn'], $hashes); + } + } +} diff --git a/tests/src/Component/PhpFilesIteratorTest.php b/tests/src/Component/PhpFilesIteratorTest.php new file mode 100644 index 0000000..2b3de93 --- /dev/null +++ b/tests/src/Component/PhpFilesIteratorTest.php @@ -0,0 +1,34 @@ +factory = new ApiExceptionFactory(); + } + + public function testWrongOrEmptyResponse(): void + { + $exception = $this->factory->createException(new SuccessResponse(), 200); + + self::assertInstanceOf(ApiErrorException::class, $exception); + self::assertEquals(200, $exception->getCode()); + self::assertEquals(200, $exception->getStatusCode()); + self::assertEquals('RetailCRM API Error', $exception->getMessage()); + self::assertFalse($exception->getErrorResponse()->success); + self::assertEmpty($exception->getErrorResponse()->errors); + self::assertEmpty($exception->getErrorResponse()->errorMsg); + } + + public function testNormalResponse(): void + { + $response = new ErrorResponse(); + $response->errorMsg = 'Account does not exist.'; + + $exception = $this->factory->createException($response, 404); + + self::assertInstanceOf(AccountDoesNotExistException::class, $exception); + self::assertEquals(404, $exception->getCode()); + self::assertEquals(404, $exception->getStatusCode()); + self::assertEquals($response->errorMsg, $exception->getMessage()); + self::assertEquals($response->errorMsg, $exception->getErrorResponse()->errorMsg); + self::assertFalse($exception->getErrorResponse()->success); + self::assertEmpty($exception->getErrorResponse()->errors); + } + + public function testEmptyErrorMsgResponse(): void + { + $response = new ErrorResponse(); + $response->errors = [ + 'first', + 'second' + ]; + + $exception = $this->factory->createException($response, 400); + + self::assertInstanceOf(ApiErrorException::class, $exception); + self::assertEquals(400, $exception->getCode()); + self::assertEquals(400, $exception->getStatusCode()); + self::assertEquals('first', $exception->getMessage()); + self::assertFalse($exception->getErrorResponse()->success); + self::assertEmpty($exception->getErrorResponse()->errorMsg); + self::assertCount(2, $exception->getErrorResponse()->errors); + } + + public function testMissingParameter(): void + { + $response = new ErrorResponse(); + $response->errorMsg = "Parameter 'integrationModule' is missing"; + + $exception = $this->factory->createException($response, 400); + + self::assertInstanceOf(MissingParameterException::class, $exception); + self::assertEquals(400, $exception->getCode()); + self::assertEquals(400, $exception->getStatusCode()); + self::assertEquals($response->errorMsg, $exception->getErrorResponse()->errorMsg); + self::assertFalse($exception->getErrorResponse()->success); + } + + public function testMissingApiKey(): void + { + $response = new ErrorResponse(); + $response->errorMsg = "\"apiKey\" is missing."; + + $exception = $this->factory->createException($response, 400); + + self::assertInstanceOf(MissingCredentialsException::class, $exception); + self::assertEquals(400, $exception->getCode()); + self::assertEquals(400, $exception->getStatusCode()); + self::assertEquals($response->errorMsg, $exception->getErrorResponse()->errorMsg); + self::assertFalse($exception->getErrorResponse()->success); + } + + public function testValidationError(): void + { + $response = new ErrorResponse(); + $response->errorMsg = "Errors in the entity format"; + $response->errors = ["code" => "Code prefix must match integrationCode"]; + + $exception = $this->factory->createException($response, 400); + + self::assertInstanceOf(ValidationException::class, $exception); + self::assertEquals(400, $exception->getCode()); + self::assertEquals(400, $exception->getStatusCode()); + self::assertEquals($response->errorMsg, $exception->getErrorResponse()->errorMsg); + self::assertFalse($exception->getErrorResponse()->success); + self::assertStringContainsString($exception->getMessage(), (string) $exception); + } +} diff --git a/tests/src/Factory/ClientFactoryTest.php b/tests/src/Factory/ClientFactoryTest.php new file mode 100644 index 0000000..c9985a4 --- /dev/null +++ b/tests/src/Factory/ClientFactoryTest.php @@ -0,0 +1,196 @@ +add(CacheItemPoolInterface::class, new FilesystemAdapter('test_app')); + $container->add(EventDispatcherInterface::class, EventDispatcher::class); + $container->add(ClientFactoryInterface::class, ClientFactory::class) + ->addMethodCalls([ + 'setCache' => [CacheItemPoolInterface::class], + 'setEventDispatcher' => [EventDispatcherInterface::class], + ]); + $container->add(ClientFactoryDependentService::class)->addArgument(ClientFactoryInterface::class); + + /** @var ClientFactoryDependentService $service */ + $service = $container->get(ClientFactoryDependentService::class); + $service->setHttpClient( + (new PockBuilder()) + ->matchOrigin(TestConfig::getApiUrl()) + ->throwClientException() + ->getClient() + ); + + self::assertFalse($service->isApiAccessible(TestConfig::getApiUrl(), TestConfig::getApiKey())); + } + + public function testCreateClient(): void + { + $client = (new ClientFactory())->createClient(TestConfig::getApiUrl(), TestConfig::getApiKey()); + + static::assertClientIsValid($client, PsrCachedReader::class, FilesystemAdapter::class); + } + + public function testCreateWithCache(): void + { + $client = (new ClientFactory()) + ->setCache(new ArrayAdapter()) + ->createClient(TestConfig::getApiUrl(), TestConfig::getApiKey()); + + static::assertClientIsValid($client, PsrCachedReader::class, ArrayAdapter::class); + } + + public function testCreateWithCacheDir(): void + { + $client = (new ClientFactory()) + ->setCacheDir(sys_get_temp_dir()) + ->createClient(TestConfig::getApiUrl(), TestConfig::getApiKey()); + $cacheDir = implode( + '', + [sys_get_temp_dir(), CacheDirectories::MAIN_DIR] + ); + + static::assertDirectoryExists($cacheDir); + static::assertClientIsValid( + $client, + PsrCachedReader::class, + FilesystemAdapter::class, + $cacheDir + ); + } + + public function testCreateWithDebugLogger(): void + { + $client = (new ClientFactory()) + ->setDebugLogger(new NullLogger()) + ->createClient(TestConfig::getApiUrl(), TestConfig::getApiKey()); + + static::assertClientIsValid($client, PsrCachedReader::class, FilesystemAdapter::class, '', true); + } + + public function testCreateWithPsrDeps(): void + { + $factory = new Psr17Factory(); + $client = (new ClientFactory()) + ->setHttpClient(Psr18ClientDiscovery::find()) + ->setRequestFactory($factory) + ->setStreamFactory($factory) + ->setUriFactory($factory) + ->createClient(TestConfig::getApiUrl(), TestConfig::getApiKey()); + + static::assertClientIsValid($client, PsrCachedReader::class, FilesystemAdapter::class); + } + + public function testAppendRequestHandlers(): void + { + $handler = new CallbackRequestHandler( + static function ( + RequestData $requestData, + RequestFactoryInterface $requestFactory, + StreamFactoryInterface $streamFactory, + UriFactoryInterface $uriFactory + ) { + if (null !== $requestData->request) { + $requestData->request = $requestData->request + ->withHeader('X-Rlimit-Token', 'example_token'); + } + } + ); + $mockJson = [ + 'success' => true, + 'versions' => ['9.0'] + ]; + + $client = (new ClientFactory()) + ->appendRequestHandlers([$handler]) + ->createClient(TestConfig::getApiUrl(), TestConfig::getApiKey()); + + static::assertClientIsValid($client, PsrCachedReader::class, FilesystemAdapter::class); + + /** @var \RetailCrm\Api\ResourceGroup\Api $api */ + $api = ReflectionUtils::getProperty($client, 'api'); + $requestTransformer = ReflectionUtils::getProperty($api, 'requestTransformer'); + + /** @var \RetailCrm\Api\Interfaces\HandlerInterface $requestHandler */ + $requestHandler = ReflectionUtils::getProperty($requestTransformer, 'handler'); + + self::assertInstanceOf(CallbackRequestHandler::class, $requestHandler->getLastHandler()); + + $mockBuilder = new PockBuilder(); + $mockBuilder->matchOrigin(TestConfig::getApiUrl()) + ->matchPath('/api/api-versions') + ->matchHeaders([ + 'X-Api-Key' => TestConfig::getApiKey(), + 'X-Rlimit-Token' => 'example_token' + ])->reply(200) + ->withJson($mockJson); + + ReflectionUtils::setProperty($api, 'httpClient', $mockBuilder->getClient()); + + self::assertEquals($mockJson['versions'], $client->api->apiVersions()->versions); + } + + public function testAppendResponseHandlers(): void + { + $client = (new ClientFactory()) + ->appendResponseHandlers([new CallbackResponseHandler(static function (ResponseData $responseData) { + return; + })]) + ->createClient(TestConfig::getApiUrl(), TestConfig::getApiKey()); + + static::assertClientIsValid($client, PsrCachedReader::class, FilesystemAdapter::class); + + $api = ReflectionUtils::getProperty($client, 'api'); + $responseTransformer = ReflectionUtils::getProperty($api, 'responseTransformer'); + + /** @var \RetailCrm\Api\Interfaces\HandlerInterface $responseHandler */ + $responseHandler = ReflectionUtils::getProperty($responseTransformer, 'handler'); + + self::assertInstanceOf(CallbackResponseHandler::class, $responseHandler->getLastHandler()); + } +} diff --git a/tests/src/Factory/SimpleClientFactoryTest.php b/tests/src/Factory/SimpleClientFactoryTest.php new file mode 100644 index 0000000..c1fb1d5 --- /dev/null +++ b/tests/src/Factory/SimpleClientFactoryTest.php @@ -0,0 +1,66 @@ +request = (new Psr17Factory())->createRequest('GET', 'https://example.com'); + }); + + $handler->handle($data); + + self::assertInstanceOf(RequestInterface::class, $data->request); + self::assertEquals('GET', $data->request->getMethod()); + self::assertEquals('https://example.com', (string) $data->request->getUri()); + } +} diff --git a/tests/src/Handler/Response/CallbackResponseHandlerTest.php b/tests/src/Handler/Response/CallbackResponseHandlerTest.php new file mode 100644 index 0000000..a4a8c5d --- /dev/null +++ b/tests/src/Handler/Response/CallbackResponseHandlerTest.php @@ -0,0 +1,43 @@ +createRequest('GET', 'https://example.com'), + $factory->createResponse(), + '' + ); + $handler = new CallbackResponseHandler(static function (ResponseData $responseData) { + $responseData->type = CustomersResponse::class; + }); + + $handler->handle($data); + + self::assertEquals(CustomersResponse::class, $data->type); + } +} diff --git a/tests/src/Model/Callback/Entity/Delivery/CustomerTest.php b/tests/src/Model/Callback/Entity/Delivery/CustomerTest.php new file mode 100644 index 0000000..d1b9a27 --- /dev/null +++ b/tests/src/Model/Callback/Entity/Delivery/CustomerTest.php @@ -0,0 +1,33 @@ +fromArray(['phones' => ['88005553125']], Customer::class); + + self::assertInstanceOf(Customer::class, $customer); + self::assertEquals(['88005553125'], $customer->phones); + } +} diff --git a/tests/src/ResourceGroup/AbstractApiResourceGroupTest.php b/tests/src/ResourceGroup/AbstractApiResourceGroupTest.php new file mode 100644 index 0000000..d4205f7 --- /dev/null +++ b/tests/src/ResourceGroup/AbstractApiResourceGroupTest.php @@ -0,0 +1,252 @@ + 'debug', + 'message' => sprintf('[RetailCRM API Request]: GET URL: "https://%s/api/api-versions", ', $host) . + 'Headers: "{"Host":["' . $host . '"],' . + '"User-Agent":["RetailCRM PHP API Client \/ v6.x"],"Accept":["application\/json"],' . + '"X-Api-Key":["' . TestConfig::getApiKey() . '"]}", ' . + 'Body: ""', + 'context' => [] + ], + [ + 'level' => 'debug', + 'message' => '[RetailCRM API Response]: Status: "200", Body: "{ + "success": true, + "versions": [ + "3.0", + "4.0", + "5.0" + ] +}"', + 'context' => [] + ], + ]; + $logger = new ArrayLogger(); + $mock = static::createUnversionedApiMockBuilder('api-versions'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient(), $logger); + $client->api->apiVersions(); + + self::assertEquals($logs, $logger->getMessages()); + } + + public function testSuccessRequestEvent(): void + { + /** @var SuccessRequestEvent|null $event */ + $event = null; + $json = <<<'EOF' +{ + "success": true, + "versions": [ + "3.0", + "4.0", + "5.0" + ] +} +EOF; + $dispatcher = new EventDispatcher(); + $dispatcher->subscribeTo(SuccessRequestEvent::class, static function (object $item) use (&$event) { + $event = $item; + }); + + $mock = static::createUnversionedApiMockBuilder('api-versions'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient(), null, $dispatcher); + $client->api->apiVersions(); + + self::assertInstanceOf(SuccessRequestEvent::class, $event); + self::assertNotNull($event->getResponse()); + self::assertNotEmpty($event->getResponse()->getBody()->__toString()); + self::assertInstanceOf(ApiVersionsResponse::class, $event->getResponseModel()); + self::assertInstanceOf(RequestInterface::class, $event->getRequest()); + self::assertEmpty($event->getRequest()->getBody()->__toString()); + self::assertStringContainsString( + parse_url(TestConfig::getApiUrl(), PHP_URL_HOST), + $event->getApiUrl() + ); + self::assertStringContainsString( + parse_url(TestConfig::getApiUrl(), PHP_URL_HOST), + $event->getApiDomain() + ); + self::assertEquals(TestConfig::getApiKey(), $event->getApiKey()); + } + + public function testFailureRequestEvent(): void + { + /** @var FailureRequestEvent|null $event */ + $event = null; + $json = <<<'EOF' +{ + "errorMsg": "Access denied.", + "success": false +} +EOF; + $dispatcher = new EventDispatcher(); + $dispatcher->subscribeTo(FailureRequestEvent::class, static function (object $item) use (&$event) { + $event = $item; + }); + + $mock = static::createUnversionedApiMockBuilder('api-versions'); + $mock->matchMethod(RequestMethod::GET) + ->reply(403) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient(), null, $dispatcher); + + try { + $client->api->apiVersions(); + } catch (AccessDeniedException $exception) { + } + + self::assertInstanceOf(FailureRequestEvent::class, $event); + self::assertNotNull($event->getResponse()); + self::assertNotEmpty($event->getResponse()->getBody()->__toString()); + self::assertInstanceOf(AccessDeniedException::class, $event->getException()); + self::assertEquals('Access denied.', $event->getException()->getErrorResponse()->errorMsg); + self::assertEquals(403, $event->getException()->getStatusCode()); + self::assertStringContainsString( + parse_url(TestConfig::getApiUrl(), PHP_URL_HOST), + $event->getApiUrl() + ); + self::assertStringContainsString( + parse_url(TestConfig::getApiUrl(), PHP_URL_HOST), + $event->getApiDomain() + ); + self::assertEquals(TestConfig::getApiKey(), $event->getApiKey()); + } + + /** + * @dataProvider failureRequestEventSuppressThrow + */ + public function testFailureRequestEventSuppressThrow(bool $useClientException): void + { + /** @var FailureRequestEvent $event */ + $event = null; + + $dispatcher = new EventDispatcher(); + $dispatcher->subscribeTo( + FailureRequestEvent::class, + static function (FailureRequestEvent $item) use (&$event) { + $item->suppressThrow(); + $event = $item; + } + ); + + $mock = static::createUnversionedApiMockBuilder('api-versions'); + + if ($useClientException) { + $mock->matchMethod(RequestMethod::GET) + ->throwClientException(); + } else { + $mock->matchMethod(RequestMethod::GET) + ->reply(403) + ->withJson([ + 'success' => false, + 'errorMsg' => 'Access denied.' + ]); + } + + $client = TestClientFactory::createClient($mock->getClient(), null, $dispatcher); + $client->api->apiVersions(); + + self::assertInstanceOf(FailureRequestEvent::class, $event); + self::assertInstanceOf( + $useClientException ? ClientExceptionInterface::class : ApiExceptionInterface::class, + $event->getException() + ); + } + + public function failureRequestEventSuppressThrow(): array + { + return [[true], [false]]; + } + + public function testRequestEventGetAuthenticator(): void + { + /** @var SuccessRequestEvent|null $event */ + $event = null; + $json = <<<'EOF' +{ + "success": true, + "versions": [ + "3.0", + "4.0", + "5.0" + ] +} +EOF; + $dispatcher = new EventDispatcher(); + $dispatcher->subscribeTo(SuccessRequestEvent::class, static function (object $item) use (&$event) { + $event = $item; + }); + + $mock = static::createUnversionedApiMockBuilder('api-versions', false); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient( + $mock->getClient(), + null, + $dispatcher, + new GetParameterAuthenticatorHandler(TestConfig::getApiKey()) + ); + $client->api->apiVersions(); + + self::assertEquals(TestConfig::getApiKey(), $event->getApiKey()); + } +} diff --git a/tests/src/ResourceGroup/ApiTest.php b/tests/src/ResourceGroup/ApiTest.php new file mode 100644 index 0000000..4a2b79c --- /dev/null +++ b/tests/src/ResourceGroup/ApiTest.php @@ -0,0 +1,78 @@ +matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $apiVersions = $client->api->apiVersions(); + + self::assertTrue($apiVersions->success); + self::assertEquals(["3.0", "4.0", "5.0"], $apiVersions->versions); + } + + public function testCredentials(): void + { + $json = <<<'EOF' +{ + "success": true, + "credentials": [ + "/api/integration-modules/{code}", + "/api/integration-modules/{code}/edit" + ], + "siteAccess": "access_full" +} +EOF; + + $mock = static::createUnversionedApiMockBuilder('credentials'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $credentials = $client->api->credentials(); + + self::assertTrue($credentials->success); + self::assertEquals(SiteAccess::ACCESS_FULL, $credentials->siteAccess); + self::assertEquals([ + "/api/integration-modules/{code}", + "/api/integration-modules/{code}/edit" + ], $credentials->credentials); + } +} diff --git a/tests/src/ResourceGroup/CostsTest.php b/tests/src/ResourceGroup/CostsTest.php new file mode 100644 index 0000000..949f4d4 --- /dev/null +++ b/tests/src/ResourceGroup/CostsTest.php @@ -0,0 +1,278 @@ +limit = 20; + $costsRequest->page = 1; + $costsRequest->filter = new CostFilter(); + $costsRequest->filter->sites = ['moysklad', 'aliexpress']; + $costsRequest->filter->maxSumm = 20; + + $mock = static::createApiMockBuilder('costs'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($costsRequest)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $costs = $client->costs->list($costsRequest); + + self::assertModelEqualsToResponse($json, $costs); + } + + public function testCostsCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 1 +} +EOF; + + $request = new CostsCreateRequest(); + $request->site = 'aliexpress'; + $request->cost = new Cost(); + $request->cost->sites = ['aliexpress']; + $request->cost->source = new Source(); + $request->cost->source->source = 'source'; + $request->cost->source->campaign = 'campaign'; + $request->cost->source->content = 'content'; + $request->cost->source->keyword = 'keyword'; + $request->cost->source->medium = 'medium'; + $request->cost->comment = 'comment'; + $request->cost->costItem = 'products-purchase-price'; + $request->cost->createdAt = new DateTime(); + $request->cost->dateFrom = new DateTime(); + $request->cost->dateTo = new DateTime(); + $request->cost->summ = 100.10; + + $mock = static::createApiMockBuilder('costs/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->costs->create($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCostsDelete(): void + { + $json = <<<'EOF' +{ + "success": true, + "count": 4, + "notRemovedIds": [13, 21] +} +EOF; + + $request = new CostsDeleteRequest(); + $request->ids = [2, 3, 5, 8, 13, 21]; + + $mock = static::createApiMockBuilder('costs/delete'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->costs->costsDelete($request); + + self::assertModelEqualsToResponse($json, $response); + } + + + public function testCostsUpload(): void + { + $json = <<<'EOF' +{ + "success": true, + "uploadedCosts": [1] +} +EOF; + + $request = new CostsUploadRequest(); + $cost = new Cost(); + $cost->sites = ['aliexpress']; + $cost->source = new Source(); + $cost->source->source = 'source'; + $cost->source->campaign = 'campaign'; + $cost->source->content = 'content'; + $cost->source->keyword = 'keyword'; + $cost->source->medium = 'medium'; + $cost->comment = 'comment'; + $cost->costItem = 'products-purchase-price'; + $cost->createdAt = new DateTime(); + $cost->dateFrom = new DateTime(); + $cost->dateTo = new DateTime(); + $cost->summ = 100.10; + $request->costs = [$cost]; + + $mock = static::createApiMockBuilder('costs/upload'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->costs->costsUpload($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testGet(): void + { + $json = <<<'EOF' +{ + "success": true, + "cost": { + "id": 739, + "dateFrom": "2019-03-26", + "dateTo": "2019-03-26", + "summ": 20, + "costItem": "products-purchase-price", + "createdAt": "2019-03-26 15:33:50", + "createdBy": "19", + "order": { + "id": 2452, + "number": "2452C" + }, + "sites": [ + "moysklad" + ] + } +} +EOF; + + $mock = static::createApiMockBuilder('costs/739'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $costs = $client->costs->get(739); + + self::assertModelEqualsToResponse($json, $costs); + } + + public function testDelete(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $mock = static::createApiMockBuilder('costs/739/delete'); + $mock->matchMethod(RequestMethod::POST) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->costs->delete(739); + + self::assertTrue($response->success); + } + + public function testEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 1 +} +EOF; + + $request = new CostsEditRequest(); + $request->site = 'aliexpress'; + $request->cost = new Cost(); + $request->cost->sites = ['aliexpress']; + $request->cost->source = new Source(); + $request->cost->source->source = 'source'; + $request->cost->source->campaign = 'campaign'; + $request->cost->source->content = 'content'; + $request->cost->source->keyword = 'keyword'; + $request->cost->source->medium = 'medium'; + $request->cost->comment = 'comment'; + $request->cost->costItem = 'products-purchase-price'; + $request->cost->createdAt = new DateTime(); + $request->cost->dateFrom = new DateTime(); + $request->cost->dateTo = new DateTime(); + $request->cost->summ = 100.10; + + $mock = static::createApiMockBuilder('costs/1/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->costs->edit(1, $request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/CustomFieldsTest.php b/tests/src/ResourceGroup/CustomFieldsTest.php new file mode 100644 index 0000000..38eb5ce --- /dev/null +++ b/tests/src/ResourceGroup/CustomFieldsTest.php @@ -0,0 +1,346 @@ +page = 1; + $request->limit = 20; + $request->filter = new CustomFieldFilter(); + $request->filter->entity = 'order'; + $request->filter->viewMode = ['editable']; + $request->filter->displayArea = ['customer']; + $request->filter->type = ['string']; + $request->filter->code = 'bonus'; + $request->filter->name = 'бонус'; + + $mock = static::createApiMockBuilder('custom-fields'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customFields->list($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testDictionaries(): void + { + $json = <<<'EOF' +{ + "success": true, + "pagination": { + "limit": 20, + "totalCount": 1, + "currentPage": 1, + "totalPageCount": 1 + }, + "customDictionaries": [ + { + "name": "test22", + "code": "test22", + "elements": [ + { + "name": "09:00 - 12:00", + "code": "test", + "ordering": 10 + }, + { + "name": "13:00 - 15:00", + "code": "test2", + "ordering": 20 + }, + { + "name": "alarm_01", + "code": "ms_alarm_01", + "ordering": 50 + }, + { + "name": "Справочник-alarm", + "code": "ms_spravochnik-alarm", + "ordering": 50 + } + ] + } + ] +} +EOF; + + $request = new CustomFieldsDictionariesRequest(); + $request->page = 1; + $request->limit = 20; + $request->filter = new CustomDictionaryFilter(); + $request->filter->code = 'test22'; + $request->filter->name = 'test22'; + + $mock = static::createApiMockBuilder('custom-fields/dictionaries'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customFields->dictionaries($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testDictionariesCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "code": "dictionary" +} +EOF; + + $dictionary = new CustomDictionary(); + $element = new SerializedCustomDictionaryElement(); + $element->name = 'test_1'; + $element->code = 'test_1'; + $element->ordering = 10; + $dictionary->name = 'TestDict'; + $dictionary->code = 'test_dict'; + $dictionary->elements = [$element]; + + $request = new CustomDictionaryCreateRequest($dictionary); + + $mock = static::createApiMockBuilder('custom-fields/dictionaries/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customFields->dictionariesCreate($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testDictionariesGet(): void + { + $json = <<<'EOF' +{ + "success": true, + "customDictionary": { + "name": "test", + "code": "test", + "elements": [ + { + "name": "09:00 - 12:00", + "code": "test", + "ordering": 10 + }, + { + "name": "13:00 - 15:00", + "code": "test2", + "ordering": 20 + } + ] + } +} +EOF; + + $mock = static::createApiMockBuilder('custom-fields/dictionaries/test'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customFields->dictionariesGet('test'); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testDictionariesEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "code": "dictionary" +} +EOF; + + $dictionary = new CustomDictionary(); + $element = new SerializedCustomDictionaryElement(); + $element->name = 'test_1'; + $element->code = 'test_1'; + $element->ordering = 10; + $dictionary->name = 'TestDict'; + $dictionary->elements = [$element]; + + $request = new CustomDictionaryCreateRequest($dictionary); + + $mock = static::createApiMockBuilder('custom-fields/dictionaries/test_dict/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customFields->dictionariesEdit('test_dict', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "code": "dictionary" +} +EOF; + + $field = new CustomField(); + $field->name = 'Description'; + $field->code = 'description'; + $field->type = CustomFieldType::STRING; + $field->ordering = 10; + $field->displayArea = CustomFieldDisplayArea::CUSTOMER; + $field->viewMode = CustomFieldViewMode::EDITABLE; + $field->inFilter = true; + $field->inList = true; + $field->inGroupActions = true; + + $request = new CustomFieldsCreateRequest($field); + + $mock = static::createApiMockBuilder(sprintf('custom-fields/%s/create', CustomFieldEntity::CUSTOMER)); + $mock->matchMethod(RequestMethod::POST) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customFields->create(CustomFieldEntity::CUSTOMER, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testGet(): void + { + $json = <<<'EOF' +{ + "success": true, + "customField": { + "name": "testGalka", + "code": "galka", + "required": false, + "inFilter": true, + "inList": true, + "inGroupActions": false, + "type": "boolean", + "entity": "order", + "default": "false", + "ordering": 50, + "displayArea": "customer", + "viewMode": "editable" + } +} +EOF; + + $mock = static::createApiMockBuilder('custom-fields/order/galka'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customFields->get(CustomFieldEntity::ORDER, 'galka'); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "code": "dictionary" +} +EOF; + + $field = new CustomField(); + $field->name = 'Description'; + $field->type = CustomFieldType::STRING; + $field->ordering = 10; + $field->viewMode = CustomFieldViewMode::EDITABLE; + $field->inFilter = true; + $field->inList = true; + $field->inGroupActions = true; + + $request = new CustomFieldsCreateRequest($field); + + $mock = static::createApiMockBuilder( + sprintf('custom-fields/%s/%s/edit', CustomFieldEntity::CUSTOMER, 'description') + ); + $mock->matchMethod(RequestMethod::POST) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customFields->edit(CustomFieldEntity::CUSTOMER, 'description', $request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/CustomersCorporateTest.php b/tests/src/ResourceGroup/CustomersCorporateTest.php new file mode 100644 index 0000000..e661b57 --- /dev/null +++ b/tests/src/ResourceGroup/CustomersCorporateTest.php @@ -0,0 +1,1109 @@ +filter = new CustomerCorporateFilter(); + $request->limit = 20; + $request->page = 1; + $request->filter->sites = ['moysklad', 'aliexpress']; + $request->filter->nickName = ['Test']; + $request->filter->contragentInn = '5921305044'; + + $mock = static::createApiMockBuilder('customers-corporate'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $costs = $client->customersCorporate->list($request); + + self::assertModelEqualsToResponse($json, $costs); + } + + public function testCombine(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $request = new CustomersCombineRequest(); + $request->customers = [ + new SerializedCustomerReference(2), + new SerializedCustomerReference(3), + new SerializedCustomerReference(4), + ]; + $request->resultCustomer = new SerializedCustomerReference(1); + + $mock = static::createApiMockBuilder('customers-corporate/combine'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->combine($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 1 +} +EOF; + + $address = new CustomerAddress(); + $address->text = '(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado(CO), 81236'; + + $contact = new CustomerContact(); + $contact->customer = new SerializedRelationAbstractCustomer(); + $contact->customer->externalId = 'test_10'; + $contact->customer->site = 'aliexpress'; + + $company = new Company(); + $company->name = 'Test Company'; + $company->brand = 'Test Brand'; + $company->isMain = true; + + $customer = new CustomerCorporate(); + $customer->source = new Source(); + $customer->addresses = [$address]; + $customer->customerContacts = [$contact]; + $customer->companies = [$company]; + $customer->source->source = 'chats'; + $customer->type = CustomerType::CORPORATE_CUSTOMER; + $customer->externalId = 'test_20'; + $customer->managerId = 24; + $customer->nickName = 'Test Corp Company'; + $customer->tags = [ + new CustomerTag('first'), + new CustomerTag('second'), + new CustomerTag('third'), + ]; + $customer->customFields = [ + 'galkatrue' => true + ]; + + $request = new CustomersCorporateCreateRequest(); + $request->site = 'aliexpress'; + $request->customerCorporate = $customer; + + $mock = static::createApiMockBuilder('customers-corporate/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->create($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCustomersFixExternalIds(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $request = new CustomersCorporateFixExternalIdsRequest(); + $request->customersCorporate = [ + new FixExternalRow(1, 'external_id_1'), + new FixExternalRow(2, 'external_id_2'), + new FixExternalRow(3, 'external_id_3'), + ]; + + $mock = static::createApiMockBuilder('customers-corporate/fix-external-ids'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->fixExternalIds($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testHistory(): void + { + $json = <<<'EOF' +{ + "success": true, + "generatedAt": "2021-02-17 16:43:02", + "history": [ + { + "id": 4242, + "createdAt": "2021-02-17 13:28:58", + "source": "user", + "user": { + "id": 27 + }, + "field": "contact", + "oldValue": null, + "newValue": { + "id": 944 + }, + "customer": { + "id": 5041, + "site": "aliexpress" + }, + "customerContact": { + "id": 944, + "customer": { + "id": 5042 + } + } + }, + { + "id": 4243, + "createdAt": "2021-02-17 13:28:58", + "source": "user", + "user": { + "id": 27 + }, + "field": "main_contact", + "oldValue": null, + "newValue": { + "id": 944 + }, + "customer": { + "id": 5041, + "site": "aliexpress" + } + }, + { + "id": 4244, + "createdAt": "2021-02-17 13:30:12", + "source": "user", + "user": { + "id": 27 + }, + "field": "address", + "oldValue": null, + "newValue": { + "id": 3553, + "name": "Test Address" + }, + "address": { + "id": 3553, + "index": "012012", + "countryIso": "US", + "region": "region", + "city": "Washington", + "street": "street", + "building": "1", + "flat": "1", + "floor": 1, + "block": 1, + "house": "1", + "housing": "A", + "metro": "metro", + "text": "street, д. 1, стр. 1, корп. A, кв./офис 1, под. 1, эт. 1, метро metro", + "name": "Test Address", + "isMain": true + }, + "customer": { + "id": 5041, + "site": "aliexpress" + } + }, + { + "id": 4246, + "createdAt": "2021-02-17 13:32:56", + "source": "user", + "user": { + "id": 27 + }, + "field": "company", + "oldValue": null, + "newValue": { + "id": 771, + "name": "Test Company" + }, + "customer": { + "id": 5041, + "site": "aliexpress" + }, + "company": { + "id": 771, + "active": true, + "name": "Test Company", + "brand": "Test Brand", + "site": "https://example.com", + "createdAt": "2021-02-17 13:32:56", + "contragent": { + "contragentType": "legal-entity", + "legalName": "Test Company", + "legalAddress": "Test Address", + "INN": "5921305044", + "OKPO": "76844019", + "KPP": "027043442", + "OGRN": "1180284568463", + "BIK": "5901994090737233", + "bank": "Test Bank", + "bankAddress": "Test Address", + "corrAccount": "5901994090737233", + "bankAccount": "5901994090737233" + }, + "address": { + "id": 3553, + "name": "Test Address" + } + } + }, + { + "id": 4247, + "createdAt": "2021-02-17 13:32:56", + "source": "user", + "user": { + "id": 27 + }, + "field": "main_company", + "oldValue": null, + "newValue": { + "id": 771, + "name": "Test Company" + }, + "customer": { + "id": 5041, + "site": "aliexpress" + } + }, + { + "id": 4248, + "createdAt": "2021-02-17 13:33:05", + "source": "user", + "user": { + "id": 27 + }, + "field": "contact.contact_company", + "oldValue": null, + "newValue": { + "id": 160, + "customer_contact": { + "id": 944 + } + }, + "customer": { + "id": 5041, + "site": "aliexpress" + }, + "company": { + "id": 771, + "name": "Test Company" + }, + "customerContact": { + "id": 944 + } + }, + { + "id": 4249, + "createdAt": "2021-02-17 14:42:52", + "created": true, + "source": "api", + "field": "id", + "apiKey": { + "current": true + }, + "oldValue": null, + "newValue": 5043, + "customer": { + "type": "customer_corporate", + "id": 5043, + "externalId": "test_20", + "nickName": "Test Corp Company", + "customerContacts": [ + { + "id": 945, + "customer": { + "id": 5039, + "externalId": "test_10" + } + } + ], + "addresses": [ + { + "id": 3554, + "text": "(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado(CO), 81236" + } + ], + "mainAddress": { + "id": 3554 + }, + "createdAt": "2021-02-17 14:42:52", + "managerId": 24, + "vip": false, + "bad": false, + "site": "aliexpress", + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "customFields": { + "galkatrue": true + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "companies": [ + { + "id": 772, + "active": true, + "name": "Test Company", + "brand": "Test Brand", + "createdAt": "2021-02-17 14:42:52", + "contragent": { + "contragentType": "legal-entity" + }, + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "customFields": [ + + ] + } + ], + "mainCompany": { + "id": 772, + "name": "Test Company" + } + } + } + ], + "pagination": { + "limit": 20, + "totalCount": 8, + "currentPage": 1, + "totalPageCount": 1 + } +} +EOF; + + $request = new CustomersHistoryRequest(); + $request->limit = 20; + $request->page = 1; + $request->filter = new CustomerHistoryFilter(); + $request->filter->sinceId = 4241; + + $mock = static::createApiMockBuilder('customers-corporate/history'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->history($request); + + self::assertModelsCallback($json, $response, static function ($expected, $actual) { + $expected = static::clearArray($expected); + $actual = static::clearArray($actual); + + self::assertEquals($expected, $actual); + }); + } + + public function testCustomersNotes(): void + { + $json = <<<'EOF' +{ + "success": true, + "pagination": { + "limit": 20, + "totalCount": 2, + "currentPage": 1, + "totalPageCount": 1 + }, + "notes": [ + { + "customer": { + "site": "moysklad", + "id": 1057, + "externalId": "10", + "type": "customer" + }, + "id": 42, + "text": "note", + "createdAt": "2019-08-06 18:04:56" + }, + { + "customer": { + "site": "moysklad", + "id": 1057, + "externalId": "10", + "type": "customer" + }, + "id": 43, + "text": "note2", + "createdAt": "2019-08-06 18:05:27" + } + ] +} +EOF; + + $request = new CustomersNotesRequest(); + $request->limit = 20; + $request->page = 1; + $request->filter = new CustomerNoteFilter(); + $request->filter->customerExternalIds = ['10']; + $request->filter->createdAtFrom = '2019-08-06 12:00:00'; + $request->filter->text = 'note'; + + $mock = static::createApiMockBuilder('customers-corporate/notes'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $costs = $client->customersCorporate->notes($request); + + self::assertModelEqualsToResponse($json, $costs); + } + + public function testCustomersNotesCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 1 +} +EOF; + + $request = new CustomersNotesCreateRequest(); + $request->site = 'moysklad'; + $request->note = new CustomerNote(); + $request->note->customer = new Customer(); + $request->note->customer->externalId = '10'; + $request->note->managerId = 21; + $request->note->text = 'Text'; + + $mock = static::createApiMockBuilder('customers-corporate/notes/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->notesCreate($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCustomersNotesDelete(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $mock = static::createApiMockBuilder('customers-corporate/notes/1/delete'); + $mock->matchMethod(RequestMethod::POST) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->notesDelete(1); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testUpload(): void + { + $json = <<<'EOF' +{ + "success": true, + "uploadedCustomers": [{ + "id": 1, + "externalId": "test_20" + }] +} +EOF; + + $address = new CustomerAddress(); + $address->text = '(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado(CO), 81236'; + + $contact = new CustomerContact(); + $contact->customer = new SerializedRelationAbstractCustomer(); + $contact->customer->externalId = 'test_10'; + $contact->customer->site = 'aliexpress'; + + $company = new Company(); + $company->name = 'Test Company'; + $company->brand = 'Test Brand'; + $company->isMain = true; + + $customer = new CustomerCorporate(); + $customer->source = new Source(); + $customer->addresses = [$address]; + $customer->customerContacts = [$contact]; + $customer->companies = [$company]; + $customer->source->source = 'chats'; + $customer->type = CustomerType::CORPORATE_CUSTOMER; + $customer->externalId = 'test_20'; + $customer->managerId = 24; + $customer->nickName = 'Test Corp Company'; + $customer->tags = [ + new CustomerTag('first'), + new CustomerTag('second'), + new CustomerTag('third'), + ]; + $customer->customFields = [ + 'galkatrue' => true + ]; + + $request = new CustomersCorporateUploadRequest(); + $request->site = 'moysklad'; + $request->customersCorporate = [$customer]; + + $mock = static::createApiMockBuilder('customers-corporate/upload'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->upload($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCustomersGet(): void + { + $json = <<<'EOF' +{ + "success": true, + "customerCorporate": { + "type": "customer_corporate", + "id": 5043, + "externalId": "test_20", + "nickName": "Test Corp Company", + "mainAddress": { + "id": 3554 + }, + "createdAt": "2021-02-17 14:42:52", + "managerId": 24, + "vip": false, + "bad": false, + "site": "aliexpress", + "tags": [ + + ], + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": { + "galkatrue": true + }, + "personalDiscount": 0, + "mainCompany": { + "id": 772, + "name": "Test Company" + } + } +} +EOF; + + $request = new BySiteRequest(ByIdentifier::ID, 'aliexpress'); + + $mock = static::createApiMockBuilder('customers-corporate/5043'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->get(5043, $request); + + self::assertModelsCallback($json, $response, static function ($expected, $actual) { + $expected['customerCorporate']['marginSumm'] = (float)$expected['customerCorporate']['marginSumm']; + $expected['customerCorporate']['totalSumm'] = (float)$expected['customerCorporate']['totalSumm']; + $expected['customerCorporate']['averageSumm'] = (float)$expected['customerCorporate']['averageSumm']; + $expected['customerCorporate']['costSumm'] = (float)$expected['customerCorporate']['costSumm']; + $expected['customerCorporate']['personalDiscount'] = + (float)$expected['customerCorporate']['personalDiscount']; + + self::assertEquals($expected, $actual); + }); + } + + public function testAddresses(): void + { + $json = <<<'EOF' +{ + "success": true, + "addresses": [ + { + "id": 3554, + "text": "(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado(CO), 81236", + "isMain": true + } + ], + "pagination": { + "limit": 20, + "totalCount": 1, + "currentPage": 1, + "totalPageCount": 1 + } +} +EOF; + + $request = new CustomersCorporateAddressesRequest(); + $request->filter = new CustomerAddressFilter(); + $request->site = 'aliexpress'; + $request->by = ByIdentifier::ID; + $request->filter->ids = [3554]; + + $mock = static::createApiMockBuilder('customers-corporate/5043/addresses'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->addresses(5043, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testAddressesCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 3559 +} +EOF; + + $request = new CustomersCorporateAddressesCreateRequest(); + $request->address = new CustomerAddress(); + $request->site = 'aliexpress'; + $request->by = ByIdentifier::ID; + $request->address->text = '(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado(CO), 81236'; + + $mock = static::createApiMockBuilder('customers-corporate/5043/addresses/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->addressesCreate(5043, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testAddressesEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 3559 +} +EOF; + + $request = new CustomersCorporateAddressesEditRequest(); + $request->address = new CustomerAddress(); + $request->site = 'aliexpress'; + $request->by = ByIdentifier::ID; + $request->entityBy = ByIdentifier::ID; + $request->address->text = '(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado2(CO), 81236'; + + $mock = static::createApiMockBuilder('customers-corporate/5043/addresses/3559/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->addressesEdit(5043, 3559, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCompanies(): void + { + $json = <<<'EOF' +{ + "success": true, + "companies": [ + { + "isMain": true, + "id": 772, + "customer": { + "site": "aliexpress", + "id": 5043, + "externalId": "test_20", + "type": "customer_corporate" + }, + "active": true, + "name": "Test Company", + "brand": "Test Brand", + "createdAt": "2021-02-17 14:42:52", + "contragent": { + "contragentType": "legal-entity" + }, + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": [ + + ] + } + ], + "pagination": { + "limit": 20, + "totalCount": 1, + "currentPage": 1, + "totalPageCount": 1 + } +} +EOF; + + $request = new CustomersCorporateCompaniesRequest(); + $request->filter = new CompanyFilter(); + $request->site = 'aliexpress'; + $request->by = ByIdentifier::ID; + $request->filter->ids = [772]; + + $mock = static::createApiMockBuilder('customers-corporate/5043/companies'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->companies(5043, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCompaniesCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 3559 +} +EOF; + + $request = new CustomersCorporateCompaniesCreateRequest(); + $request->company = new Company(); + $request->company->address = new CustomerAddress(); + $request->site = 'aliexpress'; + $request->by = ByIdentifier::ID; + $request->company->name = 'Test Company'; + $request->company->brand = 'Test Brand'; + $request->company->address->id = 1; + $request->company->isMain = true; + + $mock = static::createApiMockBuilder('customers-corporate/5043/companies/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->companiesCreate(5043, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCompaniesEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 3559 +} +EOF; + + $request = new CustomersCorporateCompaniesEditRequest(); + $request->company = new Company(); + $request->company->address = new CustomerAddress(); + $request->site = 'aliexpress'; + $request->by = ByIdentifier::ID; + $request->entityBy = ByIdentifier::ID; + $request->company->name = 'Test Company'; + $request->company->brand = 'Test Brand'; + $request->company->address->id = 3559; + $request->company->isMain = true; + + $mock = static::createApiMockBuilder('customers-corporate/5043/companies/772/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->companiesEdit(5043, 772, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testContacts(): void + { + $json = <<<'EOF' +{ + "success": true, + "contacts": [ + { + "isMain": false, + "id": 945, + "customer": { + "id": 5039, + "externalId": "test_10", + "site": "aliexpress" + }, + "companies": [ + + ] + } + ], + "pagination": { + "limit": 20, + "totalCount": 1, + "currentPage": 1, + "totalPageCount": 1 + } +} +EOF; + + $request = new CustomersCorporateContactsRequest(); + $request->filter = new CustomerContactFilter(); + $request->site = 'aliexpress'; + $request->by = ByIdentifier::ID; + $request->filter->contactIds = [5039]; + $request->filter->contactExternalIds = ['test_10']; + + $mock = static::createApiMockBuilder('customers-corporate/5043/contacts'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->contacts(5043, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testContactsCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 949 +} +EOF; + $company = new CustomerContactCompany(); + $company->id = 776; + + $request = new CustomersCorporateContactsCreateRequest(); + $request->contact = new CustomerContact(); + $request->contact->customer = new SerializedRelationAbstractCustomer(); + $request->contact->customer->id = 4985; + $request->site = 'aliexpress'; + $request->by = ByIdentifier::ID; + $request->contact->companies = [$company]; + + $mock = static::createApiMockBuilder('customers-corporate/5043/contacts/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->contactsCreate(5043, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testContactsEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 5039 +} +EOF; + $company = new CustomerContactCompany(); + $company->id = 776; + + $request = new CustomersCorporateContactsEditRequest(); + $request->contact = new CustomerContact(); + $request->site = 'aliexpress'; + $request->by = ByIdentifier::ID; + $request->entityBy = ByIdentifier::ID; + $request->contact->companies = [$company]; + + $mock = static::createApiMockBuilder('customers-corporate/5043/contacts/5039/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->contactsEdit(5043, 5039, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 5043 +} +EOF; + + $request = new CustomersCorporateEditRequest(); + $request->customerCorporate = new CustomerCorporate(); + $request->customerCorporate->nickName = 'Test Edited Customer'; + $request->site = 'aliexpress'; + $request->by = ByIdentifier::ID; + + $mock = static::createApiMockBuilder('customers-corporate/5043/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customersCorporate->edit(5043, $request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/CustomersTest.php b/tests/src/ResourceGroup/CustomersTest.php new file mode 100644 index 0000000..00ea8e4 --- /dev/null +++ b/tests/src/ResourceGroup/CustomersTest.php @@ -0,0 +1,2699 @@ +limit = 20; + $request->page = 1; + $request->filter = new CustomerFilter(); + $request->filter->sites = ['moysklad', 'aliexpress']; + $request->filter->name = '89229112322'; + $request->filter->isContact = NumericBoolean::TRUE; + + $mock = static::createApiMockBuilder('customers'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $costs = $client->customers->list($request); + + self::assertModelEqualsToResponse($json, $costs); + } + + public function testCustomersCombine(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $request = new CustomersCombineRequest(); + $request->customers = [ + new SerializedCustomerReference(2), + new SerializedCustomerReference(3), + new SerializedCustomerReference(4), + ]; + $request->resultCustomer = new SerializedCustomerReference(1); + + $mock = static::createApiMockBuilder('customers/combine'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customers->combine($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCustomersCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 1 +} +EOF; + + $customer = new Customer(); + $customer->type = CustomerType::CUSTOMER; + $customer->externalId = 'test_10'; + $customer->managerId = 24; + $customer->contragent = new CustomerContragent(); + $customer->contragent->contragentType = ContragentType::INDIVIDUAL; + $customer->tags = [ + new CustomerTag('first'), + new CustomerTag('second'), + new CustomerTag('third'), + ]; + $customer->customFields = [ + 'galkatrue' => true + ]; + $customer->address = new CustomerAddress(); + $customer->address->text = '(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado(CO), 81236'; + $customer->firstName = 'Test'; + $customer->lastName = 'User'; + $customer->patronymic = 'Tester'; + $customer->email = 'tester@example.com'; + $customer->phones = [ + new CustomerPhone('(603) 292-6810') + ]; + + $request = new CustomersCreateRequest(); + $request->site = 'aliexpress'; + $request->customer = $customer; + + $mock = static::createApiMockBuilder('customers/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customers->create($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCustomersFixExternalIds(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $request = new CustomersFixExternalIdsRequest(); + $request->customers = [ + new FixExternalRow(1, 'external_id_1'), + new FixExternalRow(2, 'external_id_2'), + new FixExternalRow(3, 'external_id_3'), + ]; + + $mock = static::createApiMockBuilder('customers/fix-external-ids'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customers->fixExternalIds($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCustomersHistory(): void + { + $json = <<<'EOF' +{ + "success": true, + "generatedAt": "2021-02-16 15:41:11", + "history": [ + { + "id": 2692, + "createdAt": "2018-10-04 20:04:13", + "source": "code", + "field": "segments", + "oldValue": { + "code": "nizkiy-sredniy-chek" + }, + "newValue": null, + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2693, + "createdAt": "2018-10-04 20:04:13", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "normalniy-sredniy-chek" + }, + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2694, + "createdAt": "2018-10-05 13:39:00", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "problemnie" + }, + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2695, + "createdAt": "2018-10-05 13:49:08", + "created": true, + "source": "api", + "field": "id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": 982, + "customer": { + "type": "customer", + "id": 982, + "externalId": "2", + "createdAt": "2018-10-05 13:49:07", + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "Admin", + "lastName": "admin", + "email": "admin@mail.ru" + } + }, + { + "id": 2696, + "createdAt": "2018-10-05 13:51:11", + "source": "api", + "field": "phones", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "9515120000", + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2697, + "createdAt": "2018-10-05 13:54:18", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2698, + "createdAt": "2018-10-05 14:36:22", + "created": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "id", + "oldValue": null, + "newValue": 983, + "customer": { + "type": "customer", + "id": 983, + "createdAt": "2018-10-05 14:36:22", + "managerId": 23, + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "test", + "email": "" + } + }, + { + "id": 2699, + "createdAt": "2018-10-05 14:36:34", + "source": "api", + "field": "external_id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "3", + "customer": { + "id": 983, + "externalId": "3", + "site": "presta" + } + }, + { + "id": 2700, + "createdAt": "2018-10-05 15:04:36", + "source": "user", + "user": { + "id": 23 + }, + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "customer": { + "id": 971, + "externalId": "1", + "site": "BitrixMod" + } + }, + { + "id": 2701, + "createdAt": "2018-10-05 16:36:26", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nedavnie" + }, + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2702, + "createdAt": "2018-10-05 16:36:26", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nedavnie" + }, + "customer": { + "id": 983, + "externalId": "3", + "site": "presta" + } + }, + { + "id": 2703, + "createdAt": "2018-10-05 16:36:26", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "rossiya-krome-msk" + }, + "customer": { + "id": 983, + "externalId": "3", + "site": "presta" + } + }, + { + "id": 2704, + "createdAt": "2018-10-05 16:36:26", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "bez-otmen" + }, + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2705, + "createdAt": "2018-10-05 16:36:26", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "pol-ne-ukazan" + }, + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2706, + "createdAt": "2018-10-05 16:36:26", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "pol-ne-ukazan" + }, + "customer": { + "id": 983, + "externalId": "3", + "site": "presta" + } + }, + { + "id": 2707, + "createdAt": "2018-10-05 16:36:26", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "sredney-davnosti" + }, + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2708, + "createdAt": "2018-10-05 16:36:26", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "sredney-davnosti" + }, + "customer": { + "id": 983, + "externalId": "3", + "site": "presta" + } + }, + { + "id": 2709, + "createdAt": "2018-10-05 16:36:26", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-ltv" + }, + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2710, + "createdAt": "2018-10-05 16:36:26", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-ltv" + }, + "customer": { + "id": 983, + "externalId": "3", + "site": "presta" + } + }, + { + "id": 2711, + "createdAt": "2018-10-05 16:36:26", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "davnie" + }, + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2712, + "createdAt": "2018-10-05 16:36:26", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "davnie" + }, + "customer": { + "id": 983, + "externalId": "3", + "site": "presta" + } + }, + { + "id": 2713, + "createdAt": "2018-10-05 16:36:27", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "malenkaya-summa-pokupok" + }, + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2714, + "createdAt": "2018-10-05 16:36:27", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "malenkaya-summa-pokupok" + }, + "customer": { + "id": 983, + "externalId": "3", + "site": "presta" + } + }, + { + "id": 2715, + "createdAt": "2018-10-05 16:36:27", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-sredniy-chek" + }, + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2716, + "createdAt": "2018-10-05 16:36:27", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-sredniy-chek" + }, + "customer": { + "id": 983, + "externalId": "3", + "site": "presta" + } + }, + { + "id": 2717, + "createdAt": "2018-10-05 16:36:27", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "rossiya" + }, + "customer": { + "id": 983, + "externalId": "3", + "site": "presta" + } + }, + { + "id": 2718, + "createdAt": "2018-10-12 12:15:53", + "source": "user", + "user": { + "id": 19 + }, + "field": "address.building", + "oldValue": null, + "newValue": "5", + "address": { + "id": 982, + "isMain": true + }, + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2719, + "createdAt": "2018-10-12 12:15:53", + "source": "user", + "user": { + "id": 19 + }, + "field": "address.city", + "oldValue": null, + "newValue": "Москва", + "address": { + "id": 982, + "isMain": true + }, + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2720, + "createdAt": "2018-10-12 12:15:53", + "source": "user", + "user": { + "id": 19 + }, + "field": "address.region", + "oldValue": null, + "newValue": "Москва город", + "address": { + "id": 982, + "isMain": true + }, + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2721, + "createdAt": "2018-10-12 12:15:53", + "source": "user", + "user": { + "id": 19 + }, + "field": "address.street", + "oldValue": null, + "newValue": "Мира", + "address": { + "id": 982, + "isMain": true + }, + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2722, + "createdAt": "2018-10-12 13:46:37", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "moskva" + }, + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2723, + "createdAt": "2018-10-12 13:46:37", + "source": "code", + "field": "segments", + "oldValue": { + "code": "rossiya-krome-msk" + }, + "newValue": null, + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2724, + "createdAt": "2018-10-12 13:46:38", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "klienti-iz-gorodov-millionnikov" + }, + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2725, + "createdAt": "2018-10-16 16:37:21", + "created": true, + "source": "api", + "field": "id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": 984, + "customer": { + "type": "customer", + "id": 984, + "externalId": "4", + "createdAt": "2018-10-16 16:37:21", + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "Test", + "lastName": "test", + "email": "sergey_ds@gmail.com" + } + }, + { + "id": 2726, + "createdAt": "2018-10-16 16:39:55", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "pol-ne-ukazan" + }, + "customer": { + "id": 984, + "externalId": "4", + "site": "presta" + } + }, + { + "id": 2727, + "createdAt": "2018-10-16 16:39:55", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "problemnie" + }, + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2728, + "createdAt": "2018-10-16 16:39:55", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "sredney-davnosti" + }, + "customer": { + "id": 984, + "externalId": "4", + "site": "presta" + } + }, + { + "id": 2729, + "createdAt": "2018-10-16 16:39:56", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nedavnie" + }, + "customer": { + "id": 984, + "externalId": "4", + "site": "presta" + } + }, + { + "id": 2730, + "createdAt": "2018-10-16 16:39:56", + "source": "code", + "field": "segments", + "oldValue": { + "code": "bez-otmen" + }, + "newValue": null, + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2731, + "createdAt": "2018-10-16 16:39:56", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "davnie" + }, + "customer": { + "id": 984, + "externalId": "4", + "site": "presta" + } + }, + { + "id": 2732, + "createdAt": "2018-10-16 16:39:56", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-sredniy-chek" + }, + "customer": { + "id": 984, + "externalId": "4", + "site": "presta" + } + }, + { + "id": 2733, + "createdAt": "2018-10-16 18:06:04", + "source": "api", + "field": "phones", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "1245987", + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2734, + "createdAt": "2018-10-16 18:06:04", + "source": "api", + "field": "email", + "apiKey": { + "current": false + }, + "oldValue": "admin@mail.ru", + "newValue": "test12@gmail.com", + "customer": { + "id": 982, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2735, + "createdAt": "2018-10-16 18:07:18", + "deleted": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "id", + "oldValue": 974, + "newValue": null, + "customer": { + "type": "customer", + "id": 974, + "externalId": "82", + "isContact": false, + "createdAt": "2018-10-03 09:13:04", + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "TestPresta", + "phones": [ + + ] + } + }, + { + "id": 2736, + "createdAt": "2018-10-16 18:07:18", + "deleted": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "id", + "oldValue": 975, + "newValue": null, + "customer": { + "type": "customer", + "id": 975, + "externalId": "83", + "isContact": false, + "createdAt": "2018-10-03 09:16:25", + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "TestPresta", + "lastName": "TestPresta", + "phones": [ + + ] + } + }, + { + "id": 2737, + "createdAt": "2018-10-16 18:07:18", + "deleted": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "id", + "oldValue": 976, + "newValue": null, + "customer": { + "type": "customer", + "id": 976, + "externalId": "84", + "isContact": false, + "createdAt": "2018-10-03 09:55:05", + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "TestPresta", + "lastName": "TestPresta", + "phones": [ + + ] + } + }, + { + "id": 2738, + "createdAt": "2018-10-16 18:07:18", + "deleted": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "id", + "oldValue": 977, + "newValue": null, + "customer": { + "type": "customer", + "id": 977, + "externalId": "85", + "isContact": false, + "createdAt": "2018-10-03 10:22:15", + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "TestPresta", + "lastName": "TestPresta", + "email": "test@gmail.ru", + "phones": [ + + ] + } + }, + { + "id": 2739, + "createdAt": "2018-10-16 18:07:18", + "deleted": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "id", + "oldValue": 979, + "newValue": null, + "customer": { + "type": "customer", + "id": 979, + "externalId": "86", + "isContact": false, + "createdAt": "2018-10-03 16:25:32", + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "TestPresta", + "lastName": "TestPresta", + "email": "test@gmail.ru", + "phones": [ + + ] + } + }, + { + "id": 2740, + "createdAt": "2018-10-16 18:07:18", + "deleted": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "id", + "oldValue": 980, + "newValue": null, + "customer": { + "type": "customer", + "id": 980, + "externalId": "87", + "isContact": false, + "createdAt": "2018-10-03 16:32:05", + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "TestPresta", + "lastName": "TestPresta", + "email": "test@gmail.ru", + "phones": [ + + ] + } + }, + { + "id": 2741, + "createdAt": "2018-10-16 18:07:18", + "deleted": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "id", + "oldValue": 981, + "newValue": null, + "customer": { + "type": "customer", + "id": 981, + "externalId": "88", + "isContact": false, + "createdAt": "2018-10-03 16:32:09", + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "TestPresta", + "lastName": "TestPresta", + "email": "test@gmail.ru", + "phones": [ + + ] + } + }, + { + "id": 2742, + "createdAt": "2018-10-16 18:07:18", + "deleted": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "id", + "oldValue": 984, + "newValue": null, + "customer": { + "type": "customer", + "id": 984, + "externalId": "4", + "isContact": false, + "createdAt": "2018-10-16 16:37:21", + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "Test", + "lastName": "test", + "email": "sergey_ds@gmail.com", + "phones": [ + + ] + } + }, + { + "id": 2743, + "createdAt": "2018-10-16 18:07:18", + "deleted": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "id", + "oldValue": 983, + "newValue": null, + "customer": { + "type": "customer", + "id": 983, + "externalId": "3", + "isContact": false, + "createdAt": "2018-10-05 14:36:22", + "managerId": 23, + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 984, + "countryIso": "RU" + }, + "segments": [ + + ], + "firstName": "test", + "email": "", + "phones": [ + + ] + } + }, + { + "id": 2744, + "createdAt": "2018-10-16 18:07:18", + "deleted": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "id", + "oldValue": 982, + "newValue": null, + "customer": { + "type": "customer", + "id": 982, + "externalId": "2", + "isContact": false, + "createdAt": "2018-10-05 13:49:07", + "managerId": 23, + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 983, + "index": "344092", + "countryIso": "", + "city": "Ростов", + "text": "test " + }, + "segments": [ + + ], + "firstName": "Admin", + "lastName": "admin", + "email": "test12@gmail.com", + "phones": [ + { + "number": "1245987" + }, + { + "number": "9515120000" + } + ] + } + }, + { + "id": 2745, + "createdAt": "2018-10-16 18:07:40", + "created": true, + "source": "api", + "field": "id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": 985, + "customer": { + "type": "customer", + "id": 985, + "externalId": "2", + "createdAt": "2018-10-16 18:03:30", + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "Test", + "lastName": "test", + "email": "test12@gmail.com", + "phones": [ + { + "number": "1245987" + } + ] + } + }, + { + "id": 2746, + "createdAt": "2018-10-16 20:03:51", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "sredney-davnosti" + }, + "customer": { + "id": 985, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2747, + "createdAt": "2018-10-16 20:03:51", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nedavnie" + }, + "customer": { + "id": 985, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2748, + "createdAt": "2018-10-16 20:03:51", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-ltv" + }, + "customer": { + "id": 985, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2749, + "createdAt": "2018-10-16 20:03:52", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "pol-ne-ukazan" + }, + "customer": { + "id": 985, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2750, + "createdAt": "2018-10-16 20:03:52", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-sredniy-chek" + }, + "customer": { + "id": 985, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2751, + "createdAt": "2018-10-16 20:03:52", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "davnie" + }, + "customer": { + "id": 985, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2752, + "createdAt": "2018-10-16 20:03:52", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "malenkaya-summa-pokupok" + }, + "customer": { + "id": 985, + "externalId": "2", + "site": "presta" + } + }, + { + "id": 2753, + "createdAt": "2018-10-17 09:27:03", + "created": true, + "source": "api", + "field": "id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": 986, + "customer": { + "type": "customer", + "id": 986, + "createdAt": "2018-10-17 09:27:03", + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "email": "" + } + }, + { + "id": 2754, + "createdAt": "2018-10-17 09:28:15", + "created": true, + "source": "api", + "field": "id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": 987, + "customer": { + "type": "customer", + "id": 987, + "createdAt": "2018-10-17 09:28:15", + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "email": "" + } + }, + { + "id": 2755, + "createdAt": "2018-10-17 12:06:13", + "source": "code", + "field": "phones", + "oldValue": null, + "newValue": "911", + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2756, + "createdAt": "2018-10-17 12:09:38", + "source": "code", + "field": "phones", + "oldValue": null, + "newValue": "890999999999", + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2757, + "createdAt": "2018-10-17 13:37:52", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-ltv" + }, + "customer": { + "id": 986, + "site": "BitrixMod" + } + }, + { + "id": 2758, + "createdAt": "2018-10-17 13:37:52", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-ltv" + }, + "customer": { + "id": 987, + "site": "BitrixMod" + } + }, + { + "id": 2759, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "pol-ne-ukazan" + }, + "customer": { + "id": 986, + "site": "BitrixMod" + } + }, + { + "id": 2760, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "pol-ne-ukazan" + }, + "customer": { + "id": 987, + "site": "BitrixMod" + } + }, + { + "id": 2761, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "sredney-davnosti" + }, + "customer": { + "id": 986, + "site": "BitrixMod" + } + }, + { + "id": 2762, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "sredney-davnosti" + }, + "customer": { + "id": 987, + "site": "BitrixMod" + } + }, + { + "id": 2763, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nedavnie" + }, + "customer": { + "id": 986, + "site": "BitrixMod" + } + }, + { + "id": 2764, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nedavnie" + }, + "customer": { + "id": 987, + "site": "BitrixMod" + } + }, + { + "id": 2765, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": { + "code": "normalniy-sredniy-chek" + }, + "newValue": null, + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2766, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "davnie" + }, + "customer": { + "id": 986, + "site": "BitrixMod" + } + }, + { + "id": 2767, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "davnie" + }, + "customer": { + "id": 987, + "site": "BitrixMod" + } + }, + { + "id": 2768, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "malenkaya-summa-pokupok" + }, + "customer": { + "id": 986, + "site": "BitrixMod" + } + }, + { + "id": 2769, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "malenkaya-summa-pokupok" + }, + "customer": { + "id": 987, + "site": "BitrixMod" + } + }, + { + "id": 2770, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-sredniy-chek" + }, + "customer": { + "id": 986, + "site": "BitrixMod" + } + }, + { + "id": 2771, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-sredniy-chek" + }, + "customer": { + "id": 987, + "site": "BitrixMod" + } + }, + { + "id": 2772, + "createdAt": "2018-10-17 13:37:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-sredniy-chek" + }, + "customer": { + "id": 978, + "site": "moysklad" + } + }, + { + "id": 2773, + "createdAt": "2018-10-17 14:50:00", + "created": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "id", + "oldValue": null, + "newValue": 988, + "customer": { + "type": "customer", + "id": 988, + "createdAt": "2018-10-17 14:50:00", + "vip": false, + "bad": false, + "contragent": { + "contragentType": "individual" + }, + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "testing" + } + }, + { + "id": 2774, + "createdAt": "2018-10-17 16:00:27", + "created": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "id", + "oldValue": null, + "newValue": 989, + "customer": { + "type": "customer", + "id": 989, + "createdAt": "2018-10-17 16:00:27", + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "testing2" + } + }, + { + "id": 2775, + "createdAt": "2018-10-17 16:42:34", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "pol-ne-ukazan" + }, + "customer": { + "id": 988 + } + }, + { + "id": 2776, + "createdAt": "2018-10-17 16:42:34", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "pol-ne-ukazan" + }, + "customer": { + "id": 989, + "site": "presta" + } + }, + { + "id": 2777, + "createdAt": "2018-10-17 16:42:34", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "sredney-davnosti" + }, + "customer": { + "id": 988 + } + }, + { + "id": 2778, + "createdAt": "2018-10-17 16:42:34", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "sredney-davnosti" + }, + "customer": { + "id": 989, + "site": "presta" + } + }, + { + "id": 2779, + "createdAt": "2018-10-17 16:42:35", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nedavnie" + }, + "customer": { + "id": 988 + } + }, + { + "id": 2780, + "createdAt": "2018-10-17 16:42:35", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nedavnie" + }, + "customer": { + "id": 989, + "site": "presta" + } + }, + { + "id": 2781, + "createdAt": "2018-10-17 16:42:35", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "rossiya-krome-msk" + }, + "customer": { + "id": 988 + } + }, + { + "id": 2782, + "createdAt": "2018-10-17 16:42:35", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "rossiya-krome-msk" + }, + "customer": { + "id": 989, + "site": "presta" + } + }, + { + "id": 2783, + "createdAt": "2018-10-17 16:42:35", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-sredniy-chek" + }, + "customer": { + "id": 988 + } + }, + { + "id": 2784, + "createdAt": "2018-10-17 16:42:35", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "nizkiy-sredniy-chek" + }, + "customer": { + "id": 989, + "site": "presta" + } + }, + { + "id": 2785, + "createdAt": "2018-10-17 16:42:35", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "rossiya" + }, + "customer": { + "id": 988 + } + }, + { + "id": 2786, + "createdAt": "2018-10-17 16:42:35", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "rossiya" + }, + "customer": { + "id": 989, + "site": "presta" + } + }, + { + "id": 2787, + "createdAt": "2018-10-17 16:42:35", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "davnie" + }, + "customer": { + "id": 988 + } + }, + { + "id": 2788, + "createdAt": "2018-10-17 16:42:35", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "davnie" + }, + "customer": { + "id": 989, + "site": "presta" + } + }, + { + "id": 2789, + "createdAt": "2018-10-17 17:11:17", + "created": true, + "source": "api", + "field": "id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": 990, + "customer": { + "type": "customer", + "id": 990, + "externalId": "3", + "createdAt": "2018-10-17 17:11:16", + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "segments": [ + + ], + "firstName": "Testadd", + "lastName": "Adddd", + "email": "test11@gmail.com" + } + }, + { + "id": 2790, + "createdAt": "2018-10-17 20:03:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "pol-ne-ukazan" + }, + "customer": { + "id": 990, + "externalId": "3", + "site": "presta" + } + }, + { + "id": 2791, + "createdAt": "2018-10-17 20:03:53", + "source": "code", + "field": "segments", + "oldValue": null, + "newValue": { + "code": "sredney-davnosti" + }, + "customer": { + "id": 990, + "externalId": "3", + "site": "presta" + } + } + ], + "pagination": { + "limit": 100, + "totalCount": 28676, + "currentPage": 1, + "totalPageCount": 287 + } +} +EOF; + + $request = new CustomersHistoryRequest(); + $request->limit = 20; + $request->page = 1; + $request->filter = new CustomerHistoryFilter(); + $request->filter->sinceId = 2691; + + $mock = static::createApiMockBuilder('customers/history'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customers->history($request); + + self::assertModelEqualsToResponse($json, $response, true); + } + + public function testCustomersNotes(): void + { + $json = <<<'EOF' +{ + "success": true, + "pagination": { + "limit": 20, + "totalCount": 2, + "currentPage": 1, + "totalPageCount": 1 + }, + "notes": [ + { + "customer": { + "site": "moysklad", + "id": 1057, + "externalId": "10", + "type": "customer" + }, + "id": 42, + "text": "note", + "createdAt": "2019-08-06 18:04:56" + }, + { + "customer": { + "site": "moysklad", + "id": 1057, + "externalId": "10", + "type": "customer" + }, + "id": 43, + "text": "note2", + "createdAt": "2019-08-06 18:05:27" + } + ] +} +EOF; + + $request = new CustomersNotesRequest(); + $request->limit = 20; + $request->page = 1; + $request->filter = new CustomerNoteFilter(); + $request->filter->customerExternalIds = ['10']; + $request->filter->createdAtFrom = '2019-08-06 12:00:00'; + $request->filter->text = 'note'; + + $mock = static::createApiMockBuilder('customers/notes'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $costs = $client->customers->notes($request); + + self::assertModelEqualsToResponse($json, $costs); + } + + public function testCustomersNotesCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 1 +} +EOF; + + $request = new CustomersNotesCreateRequest(); + $request->site = 'moysklad'; + $request->note = new CustomerNote(); + $request->note->customer = new Customer(); + $request->note->customer->externalId = '10'; + $request->note->managerId = 21; + $request->note->text = 'Text'; + + $mock = static::createApiMockBuilder('customers/notes/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customers->notesCreate($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCustomersNotesDelete(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $mock = static::createApiMockBuilder('customers/notes/1/delete'); + $mock->matchMethod(RequestMethod::POST) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customers->notesDelete(1); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCustomersUpload(): void + { + $json = <<<'EOF' +{ + "success": true, + "uploadedCustomers": [{ + "id": 1, + "externalId": "test_10" + }] +} +EOF; + + $customer = new Customer(); + $customer->type = CustomerType::CUSTOMER; + $customer->externalId = 'test_10'; + $customer->managerId = 24; + $customer->contragent = new CustomerContragent(); + $customer->contragent->contragentType = ContragentType::INDIVIDUAL; + $customer->tags = [ + new CustomerTag('first'), + new CustomerTag('second'), + new CustomerTag('third'), + ]; + $customer->customFields = [ + 'galkatrue' => true + ]; + $customer->address = new CustomerAddress(); + $customer->address->text = '(719) 395-5645 13990 W County 270 Rd Nathrop, Colorado(CO), 81236'; + $customer->firstName = 'Test'; + $customer->lastName = 'User'; + $customer->patronymic = 'Tester'; + $customer->email = 'tester@example.com'; + $customer->phones = [ + new CustomerPhone('(603) 292-6810') + ]; + + $request = new CustomersUploadRequest(); + $request->site = 'aliexpress'; + $request->customers = [$customer]; + + $mock = static::createApiMockBuilder('customers/upload'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customers->upload($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCustomersGet(): void + { + $json = <<<'EOF' +{ + "success": true, + "customer": { + "type": "customer", + "id": 4770, + "externalId": "5", + "isContact": false, + "createdAt": "2022-03-11 15:39:08", + "managerId": 24, + "vip": false, + "bad": false, + "site": "bb_demo", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + { + "name": "test", + "color": "#3e89b6", + "attached": false + }, + { + "name": "first", + "color": "#eff8e3", + "attached": false + }, + { + "name": "third", + "color": "#3e89b6", + "attached": false + }, + { + "name": "second", + "color": "#ef5e67", + "attached": false + } + ], + "marginSumm": 7057, + "totalSumm": 7057, + "averageSumm": 7057, + "ordersCount": 1, + "costSumm": 0, + "customFields": { + "galkatrue": true + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 3492, + "text": "ул. Красноармейская, д. 63/90, кв. 45" + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 77, + "active": true + }, + { + "id": 14, + "code": "malenkaya-summa-pokupok", + "name": "Маленькая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1040, + "active": true + }, + { + "id": 16, + "code": "sredniy-ltv", + "name": "Средний LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 21, + "active": true + }, + { + "id": 19, + "code": "normalniy-sredniy-chek", + "name": "Нормальный средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 20, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1284, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1281, + "active": true + } + ], + "firstName": "Omega", + "email": "omega@omega.com", + "emailMarketingUnsubscribedAt": "2020-10-30 14:22:12", + "phones": [ + { + "number": "79094055045" + }, + { + "number": "+79094055044" + } + ] + } +} +EOF; + + $request = new BySiteRequest(ByIdentifier::ID, 'bb_demo'); + + $mock = static::createApiMockBuilder('customers/4770'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customers->get(4770, $request); + + self::assertModelsCallback($json, $response, static function ($expected, $actual) { + $actualTags = $actual['customer']['tags']; + $actual['customer']['tags'] = array_filter( + array_map(static function ($tag) use ($actualTags) { + if (in_array($tag['name'], $actualTags, true)) { + return $tag; + } + }, $expected['customer']['tags']) + ); + + $expected['customer']['marginSumm'] = (float)$expected['customer']['marginSumm']; + $expected['customer']['totalSumm'] = (float)$expected['customer']['totalSumm']; + $expected['customer']['averageSumm'] = (float)$expected['customer']['averageSumm']; + $expected['customer']['costSumm'] = (float)$expected['customer']['costSumm']; + $expected['customer']['personalDiscount'] = (float)$expected['customer']['personalDiscount']; + $expected['customer']['cumulativeDiscount'] = (float)$expected['customer']['cumulativeDiscount']; + + self::assertEquals($expected, $actual); + }); + } + + public function testCustomersEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 4770 +} +EOF; + + $request = new CustomersEditRequest(); + $request->customer = new Customer(); + $request->by = ByIdentifier::ID; + $request->site = 'aliexpress'; + $request->customer->firstName = 'Test'; + + $mock = static::createApiMockBuilder('customers/4770/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->customers->edit(4770, $request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/DeliveryTest.php b/tests/src/ResourceGroup/DeliveryTest.php new file mode 100644 index 0000000..3dcc318 --- /dev/null +++ b/tests/src/ResourceGroup/DeliveryTest.php @@ -0,0 +1,327 @@ +address = new OrderDeliveryAddress(); + $delivery->date = (new DateTime())->add(new DateInterval('P1D')); + $delivery->time = TimeInterval::withCustomInterval('from 9:00 am to 18:00 pm'); + $delivery->address->index = '12010'; + $delivery->address->building = '9850'; + $delivery->address->countryIso = CountryCodeIso3166::UNITED_STATES_OF_AMERICA; + $delivery->address->city = 'New York'; + $delivery->address->street = 'Griffin Ave.'; + + $item = new SerializedOrderProduct(); + $item->initialPrice = 1000.0; + $item->discountManualPercent = 5.0; + $item->quantity = 10; + + $order = new SerializedOrder(); + $order->delivery = $delivery; + $order->items = [$item]; + $order->height = 100; + $order->width = 100; + $order->weight = 100; + $order->length = 100; + + $request = new DeliveryCalculateRequest(); + $request->order = $order; + $request->deliveryTypeCodes = ['2', '3', '8', '9', '10', '11']; + + $mock = static::createApiMockBuilder('delivery/calculate'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->delivery->calculate($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testTracking() + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $status = new StatusInfo(); + $status->code = 'code'; + $status->comment = 'comment'; + $status->updatedAt = new DateTime(); + + $item = new RequestStatusUpdateItem(); + $item->deliveryId = 'boxberry'; + $item->trackNumber = 'track'; + $item->cost = 100; + $item->history = [$status]; + + $request = new TrackingRequest(); + $request->statusUpdate = [$item]; + + $mock = static::createApiMockBuilder('delivery/generic/boxberry-1-5f8064212c612/tracking'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->delivery->tracking('boxberry-1-5f8064212c612', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testShipments() + { + $json = <<<'EOF' +{ + "success": true, + "pagination": { + "limit": 20, + "totalCount": 1, + "currentPage": 1, + "totalPageCount": 1 + }, + "deliveryShipments": [ + { + "integrationCode": "boxberry-249", + "id": 9, + "externalId": "13825126", + "deliveryType": "boxberry", + "store": "main1", + "managerId": 19, + "status": "processing", + "date": "2021-02-15", + "time": { + "from": "18:00", + "to": "22:00" + }, + "orders": [ + { + "id": 6911, + "number": "6911C" + } + ], + "extraData": { + "test": "string" + } + } + ] +} +EOF; + + $request = new DeliveryShipmentsRequest(); + $request->filter = new ApiDeliveryShipmentFilterType(); + $request->filter->dateFrom = '2020-01-15'; + $request->filter->orderNumber = '6911C'; + + $mock = static::createApiMockBuilder('delivery/shipments'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->delivery->shipments($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testShipmentsCreate() + { + $json = <<<'EOF' +{ + "success": true, + "id": 1, + "status": "processing" +} +EOF; + + $shipment = new DeliveryShipment(); + $shipment->integrationCode = 'boxberry-249'; + $shipment->externalId = 'test_30'; + $shipment->managerId = 19; + $shipment->store = 'main1'; + $shipment->date = new DateTime(); + $shipment->time = TimeInterval::withTextInterval('18:00', '22:00'); + $shipment->orders = [ + SerializedEntityOrder::withNumber('8124705923428910') + ]; + + $request = new DeliveryShipmentsCreateRequest(); + $request->site = 'aliexpress'; + $request->deliveryType = 'boxberry'; + $request->deliveryShipment = $shipment; + + $mock = static::createApiMockBuilder('delivery/shipments/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->delivery->shipmentsCreate($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testShipmentsGet() + { + $json = <<<'EOF' +{ + "success": true, + "deliveryShipment": { + "integrationCode": "boxberry-249", + "id": 9, + "externalId": "13825126", + "deliveryType": "boxberry", + "store": "main1", + "managerId": 19, + "status": "processing", + "date": "2021-02-15", + "time": { + "from": "18:00", + "to": "22:00" + }, + "orders": [ + { + "id": 6911, + "number": "6911C" + } + ], + "extraData": { + "test": "string" + } + } +} +EOF; + + $mock = static::createApiMockBuilder('delivery/shipments/9'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->delivery->shipmentsGet('9'); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testShipmentsEdit() + { + $json = <<<'EOF' +{ + "success": true, + "id": 1, + "status": "processing" +} +EOF; + + $shipment = new DeliveryShipment(); + $shipment->integrationCode = 'boxberry-249'; + $shipment->externalId = 'test_30'; + $shipment->managerId = 19; + $shipment->store = 'main1'; + $shipment->date = new DateTime(); + $shipment->time = TimeInterval::withTextInterval('18:00', '22:00'); + $shipment->orders = [ + SerializedEntityOrder::withNumber('8124705923428910') + ]; + + $request = new DeliveryShipmentsCreateRequest(); + $request->site = 'aliexpress'; + $request->deliveryType = 'boxberry'; + $request->deliveryShipment = $shipment; + + $mock = static::createApiMockBuilder('delivery/shipments/1/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->delivery->shipmentsEdit('1', $request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/ErrorTest.php b/tests/src/ResourceGroup/ErrorTest.php new file mode 100644 index 0000000..abcf74e --- /dev/null +++ b/tests/src/ResourceGroup/ErrorTest.php @@ -0,0 +1,103 @@ +expectException(ApiErrorException::class); + $this->expectExceptionCode(405); + $this->expectExceptionMessage('Method Not Allowed'); + + $mock = static::createUnversionedApiMockBuilder('api-versions'); + $mock->matchMethod(RequestMethod::GET) + ->reply(405) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $client->api->apiVersions(); + } + + public function testPostErrorResponseHandling(): void + { + $json = <<<'EOF' +{ + "success": false, + "errorMsg": "Invalid data" +} +EOF; + $this->expectException(ApiErrorException::class); + $this->expectExceptionCode(405); + $this->expectExceptionMessage('Invalid data'); + + $mock = static::createApiMockBuilder('costs/create'); + $mock->matchMethod(RequestMethod::POST) + ->reply(405) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $client->costs->create(new CostsCreateRequest()); + } + + public function testHtmlAccountDoesntExistHandling(): void + { + $html = <<<'EOF' + + + + 405 Not Allowed + + + +
+

405 Not Allowed

+
+
+
nginx/1.18.0
+ + + +EOF; + $this->expectException(AccountDoesNotExistException::class); + $this->expectExceptionCode(405); + $this->expectExceptionMessage('Account does not exist'); + + $mock = static::createApiMockBuilder('costs/create'); + $mock->matchMethod(RequestMethod::POST) + ->reply(405) + ->withHeader('Content-Type', 'text/html; charset=utf-8') + ->withBody($html); + + $client = TestClientFactory::createClient($mock->getClient()); + $client->costs->create(new CostsCreateRequest()); + } +} diff --git a/tests/src/ResourceGroup/FilesTest.php b/tests/src/ResourceGroup/FilesTest.php new file mode 100644 index 0000000..edd1e4f --- /dev/null +++ b/tests/src/ResourceGroup/FilesTest.php @@ -0,0 +1,202 @@ +filter = new FileFilter(); + $request->filter->sites = ['moysklad', 'aliexpress']; + $request->filter->sizeTo = 20; + $request->page = 1; + $request->limit = PaginationLimit::LIMIT_20; + + $mock = static::createApiMockBuilder('files'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->files->list($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testUpload(): void + { + $json = <<<'EOF' +{ + "success": true, + "file": { + "id": 1 + } +} +EOF; + + $mock = static::createApiMockBuilder('files/upload'); + $file = Psr17FactoryDiscovery::findStreamFactory()->createStream('test data'); + $request = new FilesUploadRequest($file); + + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->files->upload($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testGet(): void + { + $json = <<<'EOF' +{ + "success": true, + "file": { + "id": 25, + "filename": "API upload 20-02-2021 18:36:47", + "type": "text/plain", + "createdAt": "2021-02-20 18:36:47", + "size": 14, + "attachment": [ + + ] + } +} +EOF; + + $mock = static::createApiMockBuilder('files/25'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->files->get(25); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testDelete(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $mock = static::createApiMockBuilder('files/1/delete'); + $mock->matchMethod(RequestMethod::POST) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->files->delete(1); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testDownload(): void + { + $mock = static::createApiMockBuilder('files/25/download'); + $fileData = 'test data'; + + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withHeader('Content-Disposition', 'attachment; filename="filename.txt"') + ->withBody($fileData); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->files->download(25); + + self::assertEquals('filename.txt', $response->fileName); + self::assertEquals($fileData, $response->data->getContents()); + } + + public function testEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "file": { + "id": 1 + } +} +EOF; + + $request = new FilesEditRequest(); + $request->file = new File(); + $request->file->filename = 'Test File.xml'; + + $mock = static::createApiMockBuilder('files/1/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->files->edit(1, $request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/IntegrationTests.php b/tests/src/ResourceGroup/IntegrationTests.php new file mode 100644 index 0000000..552d811 --- /dev/null +++ b/tests/src/ResourceGroup/IntegrationTests.php @@ -0,0 +1,157 @@ +matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->integration->get('mg-fbmessenger'); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testMgTransportEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "info": { + "mgTransport": { + "endpointUrl": "https://mg-s1.retailcrm.pro", + "token": "token" + } + } +} +EOF; + $module = new IntegrationModule(); + $module->integrations = new Integrations(); + $module->integrations->mgTransport = new TransportConfiguration(); + + $module->code = 'mg-fbmessenger'; + $module->clientId = 'e029f3dd545147c6428d12d9524f33b806e9310947430773c6c82719e4c41904'; + $module->integrationCode = 'mg-fbmessenger'; + $module->name = 'Facebook Messenger'; + $module->logo = 'https://mg-tp-fbm-s1.retailcrm.pro/static/fbmessenger_logo.svg'; + $module->baseUrl = 'https://mg-tp-fbm-s1.retailcrm.pro/'; + $module->actions = ['activity' => '/actions/activity']; + $module->accountUrl = 'https://mg-tp-fbm-s1.retailcrm.pro/settings/clientId'; + $module->integrations->mgTransport->webhookUrl = 'https://mg-tp-fbm-s1.retailcrm.pro/webhook/'; + + $request = new IntegrationModulesEditRequest($module); + + $mock = static::createApiMockBuilder('integration-modules/mg-fbmessenger/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->integration->edit('mg-fbmessenger', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testPaymentEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "info": [] +} +EOF; + $module = new IntegrationModule(); + $module->integrations = new Integrations(); + $module->integrations->payment = new PaymentConfiguration(); + $module->integrations->payment->actions = new Actions(); + + $module->code = 'test-payment-integration'; + $module->clientId = 'test-payment-integration'; + $module->integrationCode = 'test-payment-integration'; + $module->name = 'Test Payment Integration'; + $module->baseUrl = 'https://example.com'; + $module->accountUrl = 'https://example.com'; + $module->integrations->payment->shops = [ + new Shop('moysklad', 'МойСклад', true), + new Shop('aliexpress', 'AliExpress', true), + ]; + $module->integrations->payment->currencies = [Currency::USD, Currency::EUR, Currency::RUB]; + $module->integrations->payment->invoiceTypes = ['link']; + $module->integrations->payment->actions->create = 'payment/create'; + $module->integrations->payment->actions->approve = 'payment/approve'; + $module->integrations->payment->actions->cancel = 'payment/cancel'; + $module->integrations->payment->actions->refund = 'payment/refund'; + + $request = new IntegrationModulesEditRequest($module); + + $mock = static::createApiMockBuilder('integration-modules/test-payment-integration/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->integration->edit('test-payment-integration', $request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/LoyaltyTest.php b/tests/src/ResourceGroup/LoyaltyTest.php new file mode 100644 index 0000000..3c558f3 --- /dev/null +++ b/tests/src/ResourceGroup/LoyaltyTest.php @@ -0,0 +1,734 @@ +customer = new SerializedEntityCustomer(); + + $account->customer->id = 4787; + $account->cardNumber = '2222 3333 4444 5555'; + $account->phoneNumber = '88005553125'; + + $request->site = 'bitrix-test'; + $request->loyaltyAccount = $account; + + $mock = static::createApiMockBuilder('loyalty/account/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->loyalty->accountCreate($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testAccountActivate(): void + { + $json = <<<'EOF' +{ + "success": true, + "loyaltyAccount": { + "active": true, + "id": 159, + "phoneNumber": "88005553123", + "cardNumber": "1111 2222 3333 4444", + "amount": 0, + "createdAt": "2021-02-24 11:15:21", + "activatedAt": "2021-02-24 11:51:03", + "customFields": [] + } +} +EOF; + + $mock = static::createApiMockBuilder('loyalty/account/159/activate'); + $mock->matchMethod(RequestMethod::POST) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->loyalty->accountActivate(159); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testAccountBonusCredit(): void + { + $activationDate = new DateTime(); + $expireDate = static::dateTimeFromFormat( + DateTimeInterface::RFC3339, + $activationDate->format(DateTimeInterface::RFC3339) + )->add(new DateInterval('P14D')); + $activationDateString = $activationDate->format('Y-m-d'); + $expireDateString = $expireDate->format('Y-m-d'); + $json = <<amount = 100; + $request->activationDate = $activationDate; + $request->expireDate = $expireDate; + $request->comment = 'Monthly membership bonuses.'; + + $mock = static::createApiMockBuilder('loyalty/account/159/bonus/credit'); + $mock->matchMethod(RequestMethod::POST) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->loyalty->accountBonusCredit(159, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testAccountBonusOperations(): void + { + $json = <<<'EOF' +{ + "success": true, + "pagination": { + "limit": 20, + "totalCount": 3, + "currentPage": 1, + "totalPageCount": 1 + }, + "bonusOperations": [ + { + "type": "credit_for_order", + "createdAt": "2020-11-27 13:47:57", + "amount": 215.1, + "order": { + "id": 6473, + "externalId": "10" + }, + "bonus": { + "activationDate": "2020-11-27" + } + }, + { + "type": "charge_for_order", + "createdAt": "2020-11-27 13:45:39", + "amount": -247, + "order": { + "id": 6473, + "externalId": "10" + } + }, + { + "type": "credit_for_order", + "createdAt": "2020-11-27 13:42:37", + "amount": 347.8, + "order": { + "id": 6472, + "externalId": "9" + }, + "bonus": { + "activationDate": "2020-11-27" + } + } + ] +} +EOF; + + $request = new LoyaltyBonusOperationsRequest(); + $request->filter = new LoyaltyAccountBonusOperationsApiFilterType(); + $request->filter->createdAtFrom = DateTimeTransformer::create('2020-01-01 00:00:00'); + $request->filter->createdAtTo = DateTimeTransformer::create('2021-08-01 00:00:00'); + + $mock = static::createApiMockBuilder('loyalty/account/147/bonus/operations'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery([ + 'filter' => [ + 'createdAtFrom' => '2020-01-01 00:00:00', + 'createdAtTo' => '2021-08-01 00:00:00' + ] + ]) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->loyalty->accountBonusOperations(147, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testAccountEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "warnings": [] +} +EOF; + + $account = new LoyaltyAccount(); + $account->cardNumber = '4444 5555 6666 7777'; + $account->phoneNumber = '88005553000'; + + $request = new LoyaltyAccountEditRequest($account); + + $mock = static::createApiMockBuilder('loyalty/account/159/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->loyalty->accountEdit(159, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testAccounts(): void + { + $json = <<<'EOF' +{ + "success": true, + "pagination": { + "limit": 20, + "totalCount": 13, + "currentPage": 1, + "totalPageCount": 1 + }, + "loyaltyAccounts": [ + { + "active": true, + "id": 162, + "customer": { + "id": 5141, + "externalId": "1", + "site": "test_pl_for_stage", + "firstName": "Admin", + "lastName": "Admin" + }, + "phoneNumber": "89085139060", + "amount": 0, + "ordersSum": 0, + "nextLevelSum": 100000, + "level": { + "type": "discount", + "id": 17, + "name": "Базовый уровень", + "privilegeSize": 20, + "privilegeSizePromo": 10 + }, + "createdAt": "2021-04-16 16:32:03", + "activatedAt": "2021-04-16 16:32:03" + }, + { + "active": true, + "id": 161, + "customer": { + "id": 5101, + "externalId": "1", + "site": "new-bitrix", + "firstName": "Admin5", + "lastName": "Admin" + }, + "phoneNumber": "89085139060", + "amount": 49, + "ordersSum": 0, + "nextLevelSum": 5000, + "level": { + "type": "discount", + "id": 14, + "name": "Базовый уровень", + "privilegeSize": 10, + "privilegeSizePromo": 20 + }, + "createdAt": "2021-03-17 18:09:56", + "activatedAt": "2021-03-17 18:09:56" + }, + { + "active": true, + "id": 160, + "customer": { + "id": 4787, + "externalId": "36", + "site": "bitrix-test", + "firstName": "test871" + }, + "phoneNumber": "88005553125", + "cardNumber": "2222 3333 4444 5555", + "amount": 0, + "ordersSum": 0, + "nextLevelSum": 10000, + "level": { + "type": "discount", + "id": 16, + "name": "Скидочный", + "privilegeSize": 50, + "privilegeSizePromo": 50 + }, + "createdAt": "2021-02-24 11:17:11", + "activatedAt": "2021-02-24 11:17:11" + }, + { + "active": true, + "id": 158, + "customer": { + "id": 5011, + "externalId": "2", + "site": "bitrix-test", + "firstName": "admin2", + "lastName": "admin2" + }, + "phoneNumber": "89085139555", + "amount": 69, + "ordersSum": 0, + "level": { + "type": "discount", + "id": 12, + "name": "Бонусный", + "privilegeSize": 20, + "privilegeSizePromo": 10 + }, + "createdAt": "2021-02-01 10:15:11", + "activatedAt": "2021-02-01 10:15:11", + "lastCheckId": "b93879ba-8791-4189-90f0-71e1904b5546" + }, + { + "active": true, + "id": 157, + "customer": { + "id": 4232, + "externalId": "13", + "site": "bitrix-test", + "firstName": "Образ", + "lastName": "Испепеляющий", + "patronymic": "Фарадеевич" + }, + "phoneNumber": "89085149060", + "amount": 0, + "ordersSum": 0, + "nextLevelSum": 10000, + "level": { + "type": "discount", + "id": 16, + "name": "Скидочный", + "privilegeSize": 50, + "privilegeSizePromo": 50 + }, + "createdAt": "2021-01-14 10:54:28", + "activatedAt": "2021-01-14 10:54:28" + }, + { + "active": true, + "id": 156, + "customer": { + "id": 4925, + "externalId": "1", + "site": "bitrix-test", + "firstName": "Admin", + "lastName": "Admin" + }, + "cardNumber": "3456346346", + "amount": 635, + "ordersSum": 0, + "nextLevelSum": 10000, + "level": { + "type": "discount", + "id": 16, + "name": "Скидочный", + "privilegeSize": 50, + "privilegeSizePromo": 50 + }, + "createdAt": "2021-01-12 16:52:50", + "activatedAt": "2021-01-12 16:52:50" + }, + { + "active": true, + "id": 155, + "customer": { + "id": 4923, + "externalId": "10", + "site": "bitrix-test", + "firstName": "Артур" + }, + "phoneNumber": "89085139060", + "amount": 28, + "ordersSum": 0, + "nextLevelSum": 10000, + "level": { + "type": "discount", + "id": 16, + "name": "Скидочный", + "privilegeSize": 50, + "privilegeSizePromo": 50 + }, + "createdAt": "2020-12-14 17:42:07", + "activatedAt": "2020-12-14 18:43:40", + "confirmedPhoneAt": "2020-12-14 18:54:37", + "lastCheckId": "4c1b54fb-c079-4757-916c-ab58dd667bff" + }, + { + "active": true, + "id": 149, + "customer": { + "id": 4770, + "externalId": "5", + "site": "aliexpress", + "firstName": "Test" + }, + "phoneNumber": "+79094055044", + "amount": 655.7, + "ordersSum": 0, + "level": { + "type": "bonus_percent", + "id": 4, + "name": "Базовый уровень", + "privilegeSize": 10, + "privilegeSizePromo": 10 + }, + "createdAt": "2020-11-27 15:39:29", + "activatedAt": "2020-11-27 15:39:29" + }, + { + "active": true, + "id": 147, + "customer": { + "id": 4880, + "externalId": "4", + "site": "bb_demo", + "firstName": "Артур" + }, + "phoneNumber": "+79094055046", + "amount": 315.9, + "ordersSum": 5629, + "level": { + "type": "bonus_percent", + "id": 4, + "name": "Базовый уровень", + "privilegeSize": 10, + "privilegeSizePromo": 10 + }, + "createdAt": "2020-11-27 13:36:08", + "activatedAt": "2020-11-27 13:36:08" + }, + { + "active": true, + "id": 94, + "customer": { + "id": 4133, + "externalId": "17", + "site": "bitrix-test", + "firstName": "atest14", + "lastName": "Иванов", + "patronymic": "иванович" + }, + "phoneNumber": "+70143453900", + "amount": 0, + "ordersSum": 0, + "nextLevelSum": 10000, + "level": { + "type": "discount", + "id": 16, + "name": "Скидочный", + "privilegeSize": 50, + "privilegeSizePromo": 50 + }, + "createdAt": "2020-11-13 09:11:44", + "activatedAt": "2020-11-13 09:11:44" + }, + { + "active": true, + "id": 91, + "customer": { + "id": 4117, + "externalId": "14", + "site": "bitrix-test", + "firstName": "test11", + "lastName": "Иванов", + "patronymic": "Аркадьевич" + }, + "phoneNumber": "+70113453901", + "amount": 100, + "ordersSum": 0, + "nextLevelSum": 10000, + "level": { + "type": "discount", + "id": 16, + "name": "Скидочный", + "privilegeSize": 50, + "privilegeSizePromo": 50 + }, + "createdAt": "2020-11-13 08:27:21", + "activatedAt": "2020-11-13 08:27:21" + }, + { + "active": true, + "id": 90, + "customer": { + "id": 4113, + "externalId": "12", + "site": "bitrix-test", + "firstName": "test2021", + "lastName": "Егоров", + "patronymic": "Егорович" + }, + "phoneNumber": "+70093453901", + "amount": 0, + "ordersSum": 0, + "nextLevelSum": 10000, + "level": { + "type": "discount", + "id": 16, + "name": "Скидочный", + "privilegeSize": 50, + "privilegeSizePromo": 50 + }, + "createdAt": "2020-11-13 08:21:13", + "activatedAt": "2020-11-13 08:21:13" + }, + { + "active": true, + "id": 85, + "customer": { + "id": 4115, + "externalId": "7", + "site": "bitrix-test", + "firstName": "testesteron", + "lastName": "Иванов", + "patronymic": "Иванович" + }, + "phoneNumber": "+70033453901", + "amount": 0, + "ordersSum": 0, + "nextLevelSum": 10000, + "level": { + "type": "discount", + "id": 16, + "name": "Скидочный", + "privilegeSize": 50, + "privilegeSizePromo": 50 + }, + "createdAt": "2020-11-13 07:23:58", + "activatedAt": "2020-11-13 07:23:58" + } + ] +} +EOF; + + $request = new LoyaltyAccountsRequest(); + $request->filter = new LoyaltyAccountApiFilterType(); + $request->filter->status = AccountStatus::ACTIVATED; + + $mock = static::createApiMockBuilder('loyalty/accounts'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(self::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->loyalty->accounts($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCalculate(): void + { + $json = <<<'EOF' +{ + "success": true, + "order": { + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "privilegeType": "none", + "totalSumm": 100, + "customer": { + "personalDiscount": 0 + }, + "delivery": { + "cost": 100 + }, + "site": "bitrix-test", + "items": [ + { + "bonusesChargeTotal": 0, + "bonusesCreditTotal": 0, + "initialPrice": 0, + "discountTotal": 0, + "prices": [ + { + "price": 0, + "quantity": 10 + } + ], + "quantity": 10 + } + ] + }, + "calculations": [ + { + "privilegeType": "none", + "discount": 0, + "creditBonuses": 0, + "maxChargeBonuses": 0, + "maximum": true + } + ] +} +EOF; + $item = new SerializedOrderProduct(); + $item->offer = SerializedOrderProductOffer::withId(1); + $item->quantity = 10; + + $order = new SerializedOrder(); + $order->customer = SerializedRelationAbstractCustomer::withExternalId( + '47876750', + 'bitrix-test' + ); + $order->items = [$item]; + $order->delivery = new SerializedOrderDelivery(100); + $order->privilegeType = PrivilegeType::NONE; + + $request = new LoyaltyCalculateRequest(); + $request->site = 'bitrix-test'; + $request->bonuses = 5; + $request->order = $order; + + $mock = static::createApiMockBuilder('loyalty/calculate'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->loyalty->calculate($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testLoyalties(): void + { + $json = <<<'EOF' +{ + "success": true, + "pagination": { + "limit": 20, + "totalCount": 4, + "currentPage": 1, + "totalPageCount": 1 + }, + "loyalties": [ + { + "active": true, + "blocked": false, + "id": 3, + "name": "Новая программа", + "confirmSmsCharge": false, + "confirmSmsRegistration": false, + "createdAt": "2020-11-26 06:58:02", + "activatedAt": "2020-11-26 06:59:52" + }, + { + "active": true, + "blocked": false, + "id": 4, + "name": "Битрикс новый", + "confirmSmsCharge": false, + "confirmSmsRegistration": false, + "createdAt": "2021-03-17 18:08:02", + "activatedAt": "2021-03-17 18:09:43" + }, + { + "active": true, + "blocked": false, + "id": 1, + "name": "Тестовая программа", + "confirmSmsCharge": false, + "confirmSmsRegistration": false, + "createdAt": "2020-10-16 13:57:53", + "activatedAt": "2020-10-16 14:00:56" + } + ] +} +EOF; + + $request = new LoyaltiesRequest(); + $request->filter = new LoyaltyApiFilterType(); + $request->filter->active = NumericBoolean::TRUE; + $request->filter->blocked = NumericBoolean::FALSE; + + $mock = static::createApiMockBuilder('loyalty/loyalties'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(self::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->loyalty->loyalties($request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/OrdersTest.php b/tests/src/ResourceGroup/OrdersTest.php new file mode 100644 index 0000000..9a88667 --- /dev/null +++ b/tests/src/ResourceGroup/OrdersTest.php @@ -0,0 +1,8751 @@ +filter = new OrderFilter(); + $request->filter->ids = [7141]; + + $mock = static::createApiMockBuilder('orders'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->list($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCombine(): void + { + $json = <<<'EOF' +{ + "success": true, + "errors": [] +} +EOF; + + $request = new OrdersCombineRequest(); + $request->order = new SerializedOrderReference(7143); + $request->resultOrder = new SerializedOrderReference(7140); + $request->technique = CombineTechnique::SUMM; + + $mock = static::createApiMockBuilder('orders/combine'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->combine($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 7146, + "order": { + "slug": 7146, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 7146, + "number": "7146A", + "orderType": "test", + "orderMethod": "phone", + "privilegeType": "none", + "countryIso": "RU", + "createdAt": "2021-02-25 17:05:06", + "statusUpdatedAt": "2021-02-25 17:05:06", + "summ": 0, + "totalSumm": 0, + "prepaySum": 1000, + "purchaseSumm": 60, + "markDatetime": "2021-02-25 17:05:06", + "lastName": "User", + "firstName": "Test", + "patronymic": "Patronymic", + "phone": "89003005069", + "email": "testuser12345678901@example.com", + "call": false, + "expired": false, + "managerId": 28, + "customer": { + "type": "customer", + "id": 4924, + "isContact": false, + "createdAt": "2020-12-15 11:07:32", + "managerId": 28, + "vip": false, + "bad": false, + "site": "moysklad", + "contragent": { + "contragentType": "individual" + }, + "tags": [], + "marginSumm": -1157.1, + "totalSumm": 10224.4, + "averageSumm": 681.63, + "ordersCount": 15, + "costSumm": 11381.5, + "customFields": { + "galkatrue": true, + "moyskladexternalid": "03956a7e-3ead-11eb-0a80-017300081b64" + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 3515, + "index": "344001", + "countryIso": "RU", + "region": "Ростовская область", + "regionId": 73, + "city": "Ростов-на-Дону", + "cityId": 4298, + "cityType": "г.", + "street": "Пушкинская", + "streetId": 1583265, + "streetType": "ул.", + "building": "10", + "text": "ул. Пушкинская, д. 10" + }, + "segments": [ + { + "id": 3, + "code": "klienti-iz-gorodov-millionnikov", + "name": "Клиенты из городов-миллионников", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 64, + "active": true + }, + { + "id": 4, + "code": "rossiya", + "name": "Россия", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 206, + "active": true + }, + { + "id": 8, + "code": "rossiya-krome-msk", + "name": "Россия (кроме МСК)", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 174, + "active": true + }, + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 14, + "code": "malenkaya-summa-pokupok", + "name": "Маленькая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1053, + "active": true + }, + { + "id": 17, + "code": "nizkiy-ltv", + "name": "Низкий LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1008, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 29, + "code": "mugchini", + "name": "Мужчины", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 9, + "active": true + } + ], + "firstName": "Андрей", + "lastName": "М", + "patronymic": "В", + "sex": "male", + "presumableSex": "male", + "email": "testuser12345678901@example.com", + "phones": [ + { + "number": "89003005069" + }, + { + "number": "+79515151515" + } + ] + }, + "contact": { + "type": "customer", + "id": 4924, + "isContact": false, + "createdAt": "2020-12-15 11:07:32", + "managerId": 28, + "vip": false, + "bad": false, + "site": "moysklad", + "contragent": { + "contragentType": "individual" + }, + "tags": [], + "marginSumm": -1157.1, + "totalSumm": 10224.4, + "averageSumm": 681.63, + "ordersCount": 15, + "costSumm": 11381.5, + "customFields": { + "galkatrue": true, + "moyskladexternalid": "03956a7e-3ead-11eb-0a80-017300081b64" + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 3515, + "index": "344001", + "countryIso": "RU", + "region": "Ростовская область", + "regionId": 73, + "city": "Ростов-на-Дону", + "cityId": 4298, + "cityType": "г.", + "street": "Пушкинская", + "streetId": 1583265, + "streetType": "ул.", + "building": "10", + "text": "ул. Пушкинская, д. 10" + }, + "segments": [ + { + "id": 3, + "code": "klienti-iz-gorodov-millionnikov", + "name": "Клиенты из городов-миллионников", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 64, + "active": true + }, + { + "id": 4, + "code": "rossiya", + "name": "Россия", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 206, + "active": true + }, + { + "id": 8, + "code": "rossiya-krome-msk", + "name": "Россия (кроме МСК)", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 174, + "active": true + }, + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 14, + "code": "malenkaya-summa-pokupok", + "name": "Маленькая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1053, + "active": true + }, + { + "id": 17, + "code": "nizkiy-ltv", + "name": "Низкий LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1008, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 29, + "code": "mugchini", + "name": "Мужчины", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 9, + "active": true + } + ], + "firstName": "Андрей", + "lastName": "М", + "patronymic": "В", + "sex": "male", + "presumableSex": "male", + "email": "testuser12345678901@example.com", + "phones": [ + { + "number": "89003005069" + }, + { + "number": "+79515151515" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "cost": 0, + "netCost": 0, + "address": { + "index": "344001", + "countryIso": "RU", + "region": "Ростовская область", + "city": "г. Ростов-на-Дону", + "street": "ул. Пушкинская", + "building": "10", + "text": "ул. Пушкинская, д. 10" + } + }, + "site": "moysklad", + "status": "assembling", + "statusComment": "Assembling order", + "items": [ + { + "markingCodes": [], + "id": 11308, + "priceType": { + "code": "base" + }, + "initialPrice": 0, + "discountTotal": 0, + "prices": [ + { + "price": 0, + "quantity": 1 + } + ], + "discounts": [ + { + "type": "manual_product", + "amount": 1 + } + ], + "vatRate": "none", + "createdAt": "2021-02-25 17:05:06", + "quantity": 1, + "status": "new", + "offer": { + "displayName": "сбьорка№1445123", + "id": 61121, + "externalId": "tGunLo27jlPGmbA8BrHxY2", + "xmlId": "tGunLo27jlPGmbA8BrHxY2", + "name": "сбьорка№1445", + "article": "14451445-14451445", + "vatRate": "none", + "unit": { + "code": "796", + "name": "Штука", + "sym": "шт" + } + }, + "properties": [], + "purchasePrice": 60 + } + ], + "fullPaidAt": "2021-02-25 14:05:06", + "payments": [ + { + "id": 4554, + "status": "paid", + "type": "bank-card", + "amount": 1000, + "paidAt": "2021-02-25 14:05:06" + } + ], + "fromApi": true, + "weight": 1000, + "shipmentStore": "main12", + "shipmentDate": "2021-03-04", + "shipped": false, + "customFields": { + "galka": false, + "test_number": 0, + "otpravit_dozakaz": false + } + } +} +EOF; + + $request = new OrdersCreateRequest(); + $order = new Order(); + $payment = new Payment(); + $delivery = new SerializedOrderDelivery(); + $deliveryAddress = new OrderDeliveryAddress(); + $offer = new Offer(); + $item = new OrderProduct(); + + $payment->type = 'bank-card'; + $payment->status = 'paid'; + $payment->amount = 1000; + $payment->paidAt = new DateTime(); + + $deliveryAddress->index = '344001'; + $deliveryAddress->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; + $deliveryAddress->region = 'Ростовская область'; + $deliveryAddress->city = 'г. Ростов-на-Дону'; + $deliveryAddress->street = 'ул. Пушкинская'; + $deliveryAddress->building = '10'; + + $delivery->address = $deliveryAddress; + $delivery->cost = 0; + $delivery->netCost = 0; + + $offer->name = 'Сборка №1445123'; + $offer->displayName = 'Сборка №1445123'; + $offer->xmlId = 'tGunLo27jlPGmbA8BrHxY2'; + $offer->article = '14451445-14451445'; + $offer->unit = new Unit('796', 'Штука', 'шт'); + + $item->offer = $offer; + $item->priceType = new PriceType('base'); + $item->quantity = 1; + $item->purchasePrice = 60; + $item->discounts = [new AbstractDiscount(DiscountType::MANUAL_PRODUCT, 1)]; + + $order->delivery = $delivery; + $order->items = [$item]; + $order->payments = [$payment]; + $order->orderType = 'test'; + $order->orderMethod = 'phone'; + $order->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; + $order->firstName = 'Test'; + $order->lastName = 'User'; + $order->patronymic = 'Patronymic'; + $order->phone = '89003005069'; + $order->email = 'testuser12345678901@example.com'; + $order->managerId = 28; + $order->customer = SerializedRelationCustomer::withIdAndType( + 4924, + CustomerType::CUSTOMER + ); + $order->status = 'assembling'; + $order->statusComment = 'Assembling order'; + $order->weight = 1000; + $order->shipmentStore = 'main12'; + $order->shipmentDate = (new DateTime())->add(new DateInterval('P7D')); + $order->shipped = false; + $order->customFields = [ + "galka" => false, + "test_number" => 0, + "otpravit_dozakaz" => false, + ]; + + $request->order = $order; + $request->site = 'moysklad'; + + $mock = static::createApiMockBuilder('orders/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->create($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testFixExternalIds(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $request = new OrdersFixExternalIdsRequest(); + $request->orders = [ + new FixExternalRow(1, 'external_1'), + new FixExternalRow(2, 'external_2'), + ]; + + $mock = static::createApiMockBuilder('orders/fix-external-ids'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->fixExternalIds($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testHistory(): void + { + $json = <<<'EOF' +{ + "success": true, + "generatedAt": "2021-02-25 20:41:40", + "history": [ + { + "id": 7038, + "createdAt": "2018-10-02 13:30:06", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2031, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2031, + "number": "14", + "externalId": "14", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-02 13:00:43", + "statusUpdatedAt": "2018-10-02 13:30:06", + "summ": 779, + "totalSumm": 1395.16, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-02 13:30:06", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "russian-post", + "service": { + "name": "Воздушный транспорт", + "code": "avia", + "active": true + }, + "cost": 616.16, + "netCost": 0, + "address": { + "id": 2031, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3632, + "initialPrice": 779, + "discountTotal": 0, + "prices": [ + { + "price": 779, + "quantity": 1 + } + ], + "createdAt": "2018-10-02 13:00:43", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7039, + "createdAt": "2018-10-02 13:30:06", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2032, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2032, + "number": "15", + "externalId": "15", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-02 13:18:05", + "statusUpdatedAt": "2018-10-02 13:30:06", + "summ": 1999, + "totalSumm": 2663.96, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-02 13:30:06", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "russian-post", + "service": { + "name": "Воздушный транспорт", + "code": "avia", + "active": true + }, + "cost": 664.96, + "netCost": 0, + "address": { + "id": 2032, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3633, + "initialPrice": 1999, + "discountTotal": 0, + "prices": [ + { + "price": 1999, + "quantity": 1 + } + ], + "createdAt": "2018-10-02 13:18:05", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1422, + "status": "not-paid", + "type": "cash", + "externalId": "9", + "amount": 2663.96 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7040, + "createdAt": "2018-10-02 13:30:06", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2033, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2033, + "number": "16", + "externalId": "16", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-02 13:24:09", + "statusUpdatedAt": "2018-10-02 13:30:06", + "summ": 4999, + "totalSumm": 4999, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-02 13:30:06", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2033, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3634, + "initialPrice": 4999, + "discountTotal": 0, + "prices": [ + { + "price": 4999, + "quantity": 1 + } + ], + "createdAt": "2018-10-02 13:24:09", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1423, + "status": "not-paid", + "type": "cash", + "externalId": "10", + "amount": 4999 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7041, + "createdAt": "2018-10-03 11:28:01", + "created": true, + "source": "user", + "user": { + "id": 19 + }, + "field": "status", + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2034, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2034, + "number": "2034C", + "orderType": "eshop-individual", + "orderMethod": "phone", + "countryIso": "RU", + "createdAt": "2018-10-03 11:28:01", + "statusUpdatedAt": "2018-10-03 11:28:01", + "summ": 0, + "totalSumm": 0, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-03 11:28:01", + "firstName": "test", + "email": "test@test.test", + "call": false, + "expired": false, + "managerId": 19, + "customer": { + "type": "customer", + "id": 978, + "isContact": false, + "createdAt": "2018-10-03 11:28:01", + "managerId": 19, + "vip": false, + "bad": false, + "site": "moysklad", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 43345.42, + "totalSumm": 61632.6, + "averageSumm": 844.28, + "ordersCount": 73, + "costSumm": 18287.18, + "customFields": { + "moyskladexternalid": "2f7651a1-e8e1-11e8-9109-f8fc0005536e" + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 982, + "countryIso": "RU", + "region": "Москва город", + "regionId": 55, + "city": "Москва", + "cityId": 4995, + "cityType": "г.", + "street": "Мира", + "streetId": 1779257, + "streetType": "пр-кт.", + "building": "5", + "notes": "345645364356b45 t56 g655gb5 g", + "text": "пр-кт. Мира, д. 5, 345645364356b45 t56 g655gb5 g" + }, + "segments": [ + { + "id": 1, + "code": "moskva", + "name": "Москва", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 47, + "active": true + }, + { + "id": 3, + "code": "klienti-iz-gorodov-millionnikov", + "name": "Клиенты из городов-миллионников", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 64, + "active": true + }, + { + "id": 4, + "code": "rossiya", + "name": "Россия", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 206, + "active": true + }, + { + "id": 11, + "code": "davnie", + "name": "Давние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 935, + "active": true + }, + { + "id": 12, + "code": "bolshaya-summa-pokupok", + "name": "Большая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 4, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "svfds", + "lastName": "dsadavfdvsdfvd", + "patronymic": "vfdscdsvsddsa", + "email": "test@test.test", + "phones": [ + { + "number": "89099999999" + }, + { + "number": "532523454365436" + }, + { + "number": "890999999999" + }, + { + "number": "911" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "cost": 0, + "netCost": 0, + "address": { + "id": 2034, + "countryIso": "RU" + } + }, + "site": "moysklad", + "status": "new", + "items": [ + { + "id": 3635, + "initialPrice": 0, + "discountTotal": 0, + "prices": [ + { + "price": 0, + "quantity": 1 + } + ], + "vatRate": "none", + "createdAt": "2018-10-03 11:28:01", + "quantity": 1, + "status": "new", + "offer": { + "displayName": "кусок мяса (нормальное (никогда не видел))", + "id": 20277, + "externalId": "sEa3dPbFiBgtg2fKsx9Pz2#Vw7MCCAbjr6AURvCyB98C0", + "xmlId": "sEa3dPbFiBgtg2fKsx9Pz2#Vw7MCCAbjr6AURvCyB98C0", + "name": "кусок мяса (нормальное (никогда не видел))", + "vatRate": "none", + "unit": { + "code": "796", + "name": "Штука", + "sym": "шт" + } + }, + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "fromApi": false, + "length": 0, + "width": 0, + "height": 0, + "shipmentStore": "main12", + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7042, + "createdAt": "2018-10-03 11:28:01", + "source": "code", + "field": "customer", + "oldValue": null, + "newValue": { + "id": 978, + "site": "moysklad" + }, + "order": { + "id": 2034, + "managerId": 19, + "site": "moysklad", + "status": "new" + } + }, + { + "id": 7043, + "createdAt": "2018-10-03 11:28:31", + "source": "user", + "user": { + "id": 19 + }, + "field": "order_product.vat_rate", + "oldValue": 0, + "newValue": null, + "order": { + "id": 2034, + "managerId": 19, + "site": "moysklad", + "status": "new" + }, + "item": { + "id": 3635, + "offer": { + "id": 20277, + "externalId": "sEa3dPbFiBgtg2fKsx9Pz2#Vw7MCCAbjr6AURvCyB98C0", + "xmlId": "sEa3dPbFiBgtg2fKsx9Pz2#Vw7MCCAbjr6AURvCyB98C0" + } + } + }, + { + "id": 7044, + "createdAt": "2018-10-03 11:28:38", + "source": "user", + "user": { + "id": 19 + }, + "field": "status", + "oldValue": { + "code": "new" + }, + "newValue": { + "code": "client-confirmed" + }, + "order": { + "id": 2034, + "managerId": 19, + "site": "moysklad", + "status": "client-confirmed" + } + }, + { + "id": 7045, + "createdAt": "2018-10-03 11:30:42", + "source": "user", + "user": { + "id": 19 + }, + "field": "order_product.status", + "oldValue": { + "code": "new" + }, + "newValue": { + "code": "confirming" + }, + "order": { + "id": 2034, + "managerId": 19, + "site": "moysklad", + "status": "client-confirmed" + }, + "item": { + "id": 3635, + "offer": { + "id": 20277, + "externalId": "sEa3dPbFiBgtg2fKsx9Pz2#Vw7MCCAbjr6AURvCyB98C0", + "xmlId": "sEa3dPbFiBgtg2fKsx9Pz2#Vw7MCCAbjr6AURvCyB98C0" + } + } + }, + { + "id": 7046, + "createdAt": "2018-10-03 11:33:36", + "source": "user", + "user": { + "id": 19 + }, + "field": "order_product.status", + "oldValue": { + "code": "confirming" + }, + "newValue": { + "code": "new" + }, + "order": { + "id": 2034, + "managerId": 19, + "site": "moysklad", + "status": "client-confirmed" + }, + "item": { + "id": 3635, + "offer": { + "id": 20277, + "externalId": "sEa3dPbFiBgtg2fKsx9Pz2#Vw7MCCAbjr6AURvCyB98C0", + "xmlId": "sEa3dPbFiBgtg2fKsx9Pz2#Vw7MCCAbjr6AURvCyB98C0" + } + } + }, + { + "id": 7047, + "createdAt": "2018-10-03 11:33:47", + "source": "api", + "field": "custom_moyskladexternalid", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "0b85301d-c6e7-11e8-9ff4-34e80002118e", + "order": { + "id": 2034, + "managerId": 19, + "site": "moysklad", + "status": "client-confirmed" + } + }, + { + "id": 7048, + "createdAt": "2018-10-03 11:34:17", + "source": "user", + "user": { + "id": 19 + }, + "field": "order_product.status", + "oldValue": { + "code": "new" + }, + "newValue": { + "code": "confirming" + }, + "order": { + "id": 2034, + "managerId": 19, + "site": "moysklad", + "status": "client-confirmed" + }, + "item": { + "id": 3635, + "offer": { + "id": 20277, + "externalId": "sEa3dPbFiBgtg2fKsx9Pz2#Vw7MCCAbjr6AURvCyB98C0", + "xmlId": "sEa3dPbFiBgtg2fKsx9Pz2#Vw7MCCAbjr6AURvCyB98C0" + } + } + }, + { + "id": 7049, + "createdAt": "2018-10-03 11:35:38", + "source": "user", + "user": { + "id": 19 + }, + "field": "order_product.status", + "oldValue": { + "code": "confirming" + }, + "newValue": { + "code": "ready-for-assembly" + }, + "order": { + "id": 2034, + "managerId": 19, + "site": "moysklad", + "status": "client-confirmed" + }, + "item": { + "id": 3635, + "offer": { + "id": 20277, + "externalId": "sEa3dPbFiBgtg2fKsx9Pz2#Vw7MCCAbjr6AURvCyB98C0", + "xmlId": "sEa3dPbFiBgtg2fKsx9Pz2#Vw7MCCAbjr6AURvCyB98C0" + } + } + }, + { + "id": 7050, + "createdAt": "2018-10-04 13:18:45", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2035, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2035, + "number": "17", + "externalId": "17", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-04 13:18:43", + "statusUpdatedAt": "2018-10-04 13:18:44", + "summ": 2699, + "totalSumm": 2699, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 13:18:44", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2035, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3636, + "initialPrice": 2699, + "discountTotal": 0, + "prices": [ + { + "price": 2699, + "quantity": 1 + } + ], + "createdAt": "2018-10-04 13:18:43", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1424, + "status": "not-paid", + "type": "cash", + "amount": 2699 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7051, + "createdAt": "2018-10-04 13:18:46", + "source": "api", + "field": "payments.external_id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "11", + "order": { + "id": 2035, + "externalId": "17", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + }, + "payment": { + "id": 1424, + "type": "cash", + "externalId": "11" + } + }, + { + "id": 7052, + "createdAt": "2018-10-04 13:21:37", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2036, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2036, + "number": "18", + "externalId": "18", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-04 14:21:36", + "statusUpdatedAt": "2018-10-04 13:21:37", + "summ": 2999, + "totalSumm": 2999, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 13:21:37", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2036, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3637, + "initialPrice": 2999, + "discountTotal": 0, + "prices": [ + { + "price": 2999, + "quantity": 1 + } + ], + "createdAt": "2018-10-04 14:21:36", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1425, + "status": "not-paid", + "type": "cash", + "amount": 2999 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7053, + "createdAt": "2018-10-04 13:21:38", + "source": "api", + "field": "payments.external_id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "12", + "order": { + "id": 2036, + "externalId": "18", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + }, + "payment": { + "id": 1425, + "type": "cash", + "externalId": "12" + } + }, + { + "id": 7054, + "createdAt": "2018-10-04 13:38:04", + "source": "api", + "field": "created_at", + "apiKey": { + "current": false + }, + "oldValue": "2018-10-04 14:21:36", + "newValue": "2018-10-04 14:21:39", + "order": { + "id": 2036, + "externalId": "18", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7055, + "createdAt": "2018-10-04 13:38:04", + "source": "api", + "field": "customer_comment", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "dj", + "order": { + "id": 2036, + "externalId": "18", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7056, + "createdAt": "2018-10-04 14:39:14", + "source": "api", + "field": "customer_comment", + "apiKey": { + "current": false + }, + "oldValue": "dj", + "newValue": "djytfgyu", + "order": { + "id": 2036, + "externalId": "18", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7057, + "createdAt": "2018-10-04 14:58:43", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2037, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2037, + "number": "21", + "externalId": "21", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-04 14:58:42", + "statusUpdatedAt": "2018-10-04 14:58:42", + "summ": 4999, + "totalSumm": 4999, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 14:58:42", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2037, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3638, + "initialPrice": 4999, + "discountTotal": 0, + "prices": [ + { + "price": 4999, + "quantity": 1 + } + ], + "createdAt": "2018-10-04 14:58:42", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1426, + "status": "not-paid", + "type": "cash", + "amount": 4999 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7058, + "createdAt": "2018-10-04 14:58:44", + "source": "api", + "field": "payments.external_id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "13", + "order": { + "id": 2037, + "externalId": "21", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + }, + "payment": { + "id": 1426, + "type": "cash", + "externalId": "13" + } + }, + { + "id": 7059, + "createdAt": "2018-10-04 14:02:54", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2038, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2038, + "number": "22", + "externalId": "22", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-04 21:02:53", + "statusUpdatedAt": "2018-10-04 14:02:54", + "summ": 4398, + "totalSumm": 4398, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 14:02:54", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2038, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3639, + "initialPrice": 2199, + "discountTotal": 0, + "prices": [ + { + "price": 2199, + "quantity": 2 + } + ], + "createdAt": "2018-10-04 21:02:53", + "quantity": 2, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1427, + "status": "not-paid", + "type": "cash", + "amount": 4398 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7060, + "createdAt": "2018-10-04 14:02:54", + "source": "api", + "field": "payments.external_id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "14", + "order": { + "id": 2038, + "externalId": "22", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + }, + "payment": { + "id": 1427, + "type": "cash", + "externalId": "14" + } + }, + { + "id": 7061, + "createdAt": "2018-10-04 14:29:48", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2039, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2039, + "number": "23", + "externalId": "23", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-04 15:29:47", + "statusUpdatedAt": "2018-10-04 14:29:48", + "summ": 6597, + "totalSumm": 6597, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 14:29:48", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2039, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3640, + "initialPrice": 2199, + "discountTotal": 0, + "prices": [ + { + "price": 2199, + "quantity": 3 + } + ], + "createdAt": "2018-10-04 15:29:47", + "quantity": 3, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1428, + "status": "not-paid", + "type": "cash", + "amount": 6597 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7062, + "createdAt": "2018-10-04 14:29:49", + "source": "api", + "field": "payments.external_id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "15", + "order": { + "id": 2039, + "externalId": "23", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + }, + "payment": { + "id": 1428, + "type": "cash", + "externalId": "15" + } + }, + { + "id": 7063, + "createdAt": "2018-10-04 15:24:01", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2040, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2040, + "number": "25", + "externalId": "25", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-04 16:24:00", + "statusUpdatedAt": "2018-10-04 15:24:01", + "summ": 4999, + "totalSumm": 4999, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 15:24:01", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2040, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3641, + "initialPrice": 4999, + "discountTotal": 0, + "prices": [ + { + "price": 4999, + "quantity": 1 + } + ], + "createdAt": "2018-10-04 16:24:00", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1429, + "status": "not-paid", + "type": "cash", + "amount": 4999 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7064, + "createdAt": "2018-10-04 15:24:02", + "source": "api", + "field": "payments.external_id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "16", + "order": { + "id": 2040, + "externalId": "25", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + }, + "payment": { + "id": 1429, + "type": "cash", + "externalId": "16" + } + }, + { + "id": 7065, + "createdAt": "2018-10-04 15:27:29", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2041, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2041, + "number": "26", + "externalId": "26", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-04 15:27:27", + "statusUpdatedAt": "2018-10-04 15:27:27", + "summ": 2199, + "totalSumm": 2199, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 15:27:27", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2041, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3642, + "initialPrice": 2199, + "discountTotal": 0, + "prices": [ + { + "price": 2199, + "quantity": 1 + } + ], + "createdAt": "2018-10-04 15:27:27", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1430, + "status": "not-paid", + "type": "cash", + "amount": 2199 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7066, + "createdAt": "2018-10-04 15:27:31", + "source": "api", + "field": "payments.external_id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "17", + "order": { + "id": 2041, + "externalId": "26", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + }, + "payment": { + "id": 1430, + "type": "cash", + "externalId": "17" + } + }, + { + "id": 7067, + "createdAt": "2018-10-04 15:32:32", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2042, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2042, + "number": "27", + "externalId": "27", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-04 16:32:31", + "statusUpdatedAt": "2018-10-04 15:32:32", + "summ": 4999, + "totalSumm": 4999, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 15:32:32", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2042, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3643, + "initialPrice": 4999, + "discountTotal": 0, + "prices": [ + { + "price": 4999, + "quantity": 1 + } + ], + "createdAt": "2018-10-04 16:32:31", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1431, + "status": "not-paid", + "type": "cash", + "amount": 4999 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7068, + "createdAt": "2018-10-04 15:32:32", + "source": "api", + "field": "payments.external_id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "18", + "order": { + "id": 2042, + "externalId": "27", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + }, + "payment": { + "id": 1431, + "type": "cash", + "externalId": "18" + } + }, + { + "id": 7069, + "createdAt": "2018-10-04 15:33:32", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2043, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2043, + "number": "28", + "externalId": "28", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-04 16:33:31", + "statusUpdatedAt": "2018-10-04 15:33:31", + "summ": 4999, + "totalSumm": 4999, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 15:33:31", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2043, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3644, + "initialPrice": 4999, + "discountTotal": 0, + "prices": [ + { + "price": 4999, + "quantity": 1 + } + ], + "createdAt": "2018-10-04 16:33:31", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1432, + "status": "not-paid", + "type": "cash", + "amount": 4999 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7070, + "createdAt": "2018-10-04 15:33:32", + "source": "api", + "field": "payments.external_id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "19", + "order": { + "id": 2043, + "externalId": "28", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + }, + "payment": { + "id": 1432, + "type": "cash", + "externalId": "19" + } + }, + { + "id": 7071, + "createdAt": "2018-10-04 15:34:29", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2044, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2044, + "number": "29", + "externalId": "29", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-04 16:34:29", + "statusUpdatedAt": "2018-10-04 15:34:29", + "summ": 1999, + "totalSumm": 1999, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 15:34:29", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2044, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3645, + "initialPrice": 1999, + "discountTotal": 0, + "prices": [ + { + "price": 1999, + "quantity": 1 + } + ], + "createdAt": "2018-10-04 16:34:29", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1433, + "status": "not-paid", + "type": "cash", + "amount": 1999 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7072, + "createdAt": "2018-10-04 15:34:30", + "source": "api", + "field": "payments.external_id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "20", + "order": { + "id": 2044, + "externalId": "29", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + }, + "payment": { + "id": 1433, + "type": "cash", + "externalId": "20" + } + }, + { + "id": 7073, + "createdAt": "2018-10-04 15:35:06", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2045, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2045, + "number": "30", + "externalId": "30", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-04 16:35:05", + "statusUpdatedAt": "2018-10-04 15:35:06", + "summ": 1999, + "totalSumm": 1999, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 15:35:06", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2045, + "countryIso": "", + "text": "ntncncn" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3646, + "initialPrice": 1999, + "discountTotal": 0, + "prices": [ + { + "price": 1999, + "quantity": 1 + } + ], + "createdAt": "2018-10-04 16:35:05", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1434, + "status": "not-paid", + "type": "cash", + "amount": 1999 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7074, + "createdAt": "2018-10-04 15:35:06", + "source": "api", + "field": "payments.external_id", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "21", + "order": { + "id": 2045, + "externalId": "30", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + }, + "payment": { + "id": 1434, + "type": "cash", + "externalId": "21" + } + }, + { + "id": 7075, + "createdAt": "2018-10-04 16:30:07", + "created": true, + "source": "user", + "user": { + "id": 19 + }, + "field": "status", + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2046, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2046, + "number": "2046C", + "orderType": "eshop-individual", + "orderMethod": "phone", + "countryIso": "RU", + "createdAt": "2018-10-04 16:30:07", + "statusUpdatedAt": "2018-10-04 16:30:07", + "summ": 9522, + "totalSumm": 9522, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 16:30:07", + "firstName": "test", + "email": "test@test.test", + "call": false, + "expired": false, + "managerId": 19, + "customer": { + "type": "customer", + "id": 978, + "isContact": false, + "createdAt": "2018-10-03 11:28:01", + "managerId": 19, + "vip": false, + "bad": false, + "site": "moysklad", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 43345.42, + "totalSumm": 61632.6, + "averageSumm": 844.28, + "ordersCount": 73, + "costSumm": 18287.18, + "customFields": { + "moyskladexternalid": "2f7651a1-e8e1-11e8-9109-f8fc0005536e" + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 982, + "countryIso": "RU", + "region": "Москва город", + "regionId": 55, + "city": "Москва", + "cityId": 4995, + "cityType": "г.", + "street": "Мира", + "streetId": 1779257, + "streetType": "пр-кт.", + "building": "5", + "notes": "345645364356b45 t56 g655gb5 g", + "text": "пр-кт. Мира, д. 5, 345645364356b45 t56 g655gb5 g" + }, + "segments": [ + { + "id": 1, + "code": "moskva", + "name": "Москва", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 47, + "active": true + }, + { + "id": 3, + "code": "klienti-iz-gorodov-millionnikov", + "name": "Клиенты из городов-миллионников", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 64, + "active": true + }, + { + "id": 4, + "code": "rossiya", + "name": "Россия", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 206, + "active": true + }, + { + "id": 11, + "code": "davnie", + "name": "Давние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 935, + "active": true + }, + { + "id": 12, + "code": "bolshaya-summa-pokupok", + "name": "Большая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 4, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "svfds", + "lastName": "dsadavfdvsdfvd", + "patronymic": "vfdscdsvsddsa", + "email": "test@test.test", + "phones": [ + { + "number": "89099999999" + }, + { + "number": "532523454365436" + }, + { + "number": "890999999999" + }, + { + "number": "911" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "cost": 0, + "netCost": 0, + "address": { + "id": 2046, + "countryIso": "RU" + } + }, + "site": "moysklad", + "status": "new", + "items": [ + { + "id": 3647, + "initialPrice": 9522, + "discountTotal": 0, + "prices": [ + { + "price": 9522, + "quantity": 1 + } + ], + "vatRate": "none", + "createdAt": "2018-10-04 16:30:07", + "quantity": 1, + "status": "new", + "offer": { + "displayName": "test_complect", + "id": 20282, + "externalId": "tgGLBBeLjviVOs6wwSJpR2", + "xmlId": "tgGLBBeLjviVOs6wwSJpR2", + "name": "test_complect", + "article": "000224", + "vatRate": "none", + "unit": { + "code": "796", + "name": "Штука", + "sym": "шт" + } + }, + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "fromApi": false, + "length": 0, + "width": 0, + "height": 0, + "shipmentStore": "main12", + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7076, + "createdAt": "2018-10-04 16:30:49", + "source": "user", + "user": { + "id": 19 + }, + "field": "delivery_cost", + "oldValue": 0, + "newValue": 300, + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "new" + } + }, + { + "id": 7077, + "createdAt": "2018-10-04 16:30:49", + "source": "user", + "user": { + "id": 19 + }, + "field": "delivery_type", + "oldValue": null, + "newValue": { + "code": "courier" + }, + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "new" + } + }, + { + "id": 7078, + "createdAt": "2018-10-04 16:30:49", + "source": "user", + "user": { + "id": 19 + }, + "field": "discount_manual_percent", + "oldValue": null, + "newValue": 20, + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "new" + } + }, + { + "id": 7079, + "createdAt": "2018-10-04 16:30:49", + "source": "user", + "user": { + "id": 19 + }, + "field": "order_product.discount_total", + "oldValue": 0, + "newValue": 1904.4, + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "new" + }, + "item": { + "id": 3647, + "offer": { + "id": 20282, + "externalId": "tgGLBBeLjviVOs6wwSJpR2", + "xmlId": "tgGLBBeLjviVOs6wwSJpR2" + } + } + }, + { + "id": 7080, + "createdAt": "2018-10-04 16:30:49", + "source": "user", + "user": { + "id": 19 + }, + "field": "order_product.quantity", + "oldValue": 1, + "newValue": 4, + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "new" + }, + "item": { + "id": 3647, + "offer": { + "id": 20282, + "externalId": "tgGLBBeLjviVOs6wwSJpR2", + "xmlId": "tgGLBBeLjviVOs6wwSJpR2" + } + } + }, + { + "id": 7081, + "createdAt": "2018-10-04 16:30:49", + "source": "user", + "user": { + "id": 19 + }, + "field": "order_product.summ", + "oldValue": 9522, + "newValue": 30470.4, + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "new" + }, + "item": { + "id": 3647, + "offer": { + "id": 20282, + "externalId": "tgGLBBeLjviVOs6wwSJpR2", + "xmlId": "tgGLBBeLjviVOs6wwSJpR2" + } + } + }, + { + "id": 7082, + "createdAt": "2018-10-04 16:31:11", + "source": "user", + "user": { + "id": 19 + }, + "field": "payments", + "oldValue": null, + "newValue": { + "id": 1435, + "type": "bank-card" + }, + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "new" + }, + "payment": { + "id": 1435, + "status": "not-paid", + "type": "bank-card", + "amount": 30770.4 + } + }, + { + "id": 7083, + "createdAt": "2018-10-04 16:31:11", + "source": "user", + "user": { + "id": 19 + }, + "field": "delivery_time", + "oldValue": null, + "newValue": { + "from": "09:00", + "to": "13:00" + }, + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "new" + } + }, + { + "id": 7084, + "createdAt": "2018-10-04 16:31:20", + "source": "user", + "user": { + "id": 19 + }, + "field": "manager_comment", + "oldValue": null, + "newValue": "Комментарии оператора", + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "new" + } + }, + { + "id": 7085, + "createdAt": "2018-10-04 16:33:27", + "source": "user", + "user": { + "id": 19 + }, + "field": "order_product.vat_rate", + "oldValue": 0, + "newValue": null, + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "new" + }, + "item": { + "id": 3647, + "offer": { + "id": 20282, + "externalId": "tgGLBBeLjviVOs6wwSJpR2", + "xmlId": "tgGLBBeLjviVOs6wwSJpR2" + } + } + }, + { + "id": 7086, + "createdAt": "2018-10-04 16:33:35", + "created": true, + "source": "user", + "user": { + "id": 19 + }, + "field": "status", + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2047, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2047, + "number": "2047C", + "orderType": "eshop-individual", + "orderMethod": "phone", + "countryIso": "RU", + "createdAt": "2018-10-04 16:33:35", + "statusUpdatedAt": "2018-10-04 16:33:35", + "summ": 3597, + "totalSumm": 3597, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 16:33:35", + "firstName": "test", + "email": "test@test.test", + "call": false, + "expired": false, + "managerId": 19, + "customer": { + "type": "customer", + "id": 978, + "isContact": false, + "createdAt": "2018-10-03 11:28:01", + "managerId": 19, + "vip": false, + "bad": false, + "site": "moysklad", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 43345.42, + "totalSumm": 61632.6, + "averageSumm": 844.28, + "ordersCount": 73, + "costSumm": 18287.18, + "customFields": { + "moyskladexternalid": "2f7651a1-e8e1-11e8-9109-f8fc0005536e" + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 982, + "countryIso": "RU", + "region": "Москва город", + "regionId": 55, + "city": "Москва", + "cityId": 4995, + "cityType": "г.", + "street": "Мира", + "streetId": 1779257, + "streetType": "пр-кт.", + "building": "5", + "notes": "345645364356b45 t56 g655gb5 g", + "text": "пр-кт. Мира, д. 5, 345645364356b45 t56 g655gb5 g" + }, + "segments": [ + { + "id": 1, + "code": "moskva", + "name": "Москва", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 47, + "active": true + }, + { + "id": 3, + "code": "klienti-iz-gorodov-millionnikov", + "name": "Клиенты из городов-миллионников", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 64, + "active": true + }, + { + "id": 4, + "code": "rossiya", + "name": "Россия", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 206, + "active": true + }, + { + "id": 11, + "code": "davnie", + "name": "Давние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 935, + "active": true + }, + { + "id": 12, + "code": "bolshaya-summa-pokupok", + "name": "Большая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 4, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "svfds", + "lastName": "dsadavfdvsdfvd", + "patronymic": "vfdscdsvsddsa", + "email": "test@test.test", + "phones": [ + { + "number": "89099999999" + }, + { + "number": "532523454365436" + }, + { + "number": "890999999999" + }, + { + "number": "911" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "cost": 0, + "netCost": 0, + "address": { + "id": 2047, + "countryIso": "RU" + } + }, + "site": "moysklad", + "status": "new", + "items": [ + { + "id": 3648, + "initialPrice": 1199, + "discountTotal": 0, + "prices": [ + { + "price": 1199, + "quantity": 1 + } + ], + "createdAt": "2018-10-04 16:33:35", + "quantity": 1, + "status": "new", + "offer": { + "displayName": "Домашние Тапочки Розовый Рай", + "id": 20106, + "externalId": "210", + "xmlId": "471", + "name": "Домашние Тапочки Розовый Рай", + "article": "174-15-xx", + "vatRate": "none", + "unit": { + "code": "pc", + "name": "Штука", + "sym": "шт." + } + }, + "properties": [ + + ], + "purchasePrice": 0 + }, + { + "id": 3649, + "initialPrice": 1199, + "discountTotal": 0, + "prices": [ + { + "price": 1199, + "quantity": 1 + } + ], + "createdAt": "2018-10-04 16:33:35", + "quantity": 1, + "status": "new", + "offer": { + "displayName": "Домашние Тапочки Розовый Рай", + "id": 20105, + "externalId": "209", + "xmlId": "470", + "name": "Домашние Тапочки Розовый Рай", + "article": "174-15-xx", + "vatRate": "none", + "unit": { + "code": "pc", + "name": "Штука", + "sym": "шт." + } + }, + "properties": [ + + ], + "purchasePrice": 0 + }, + { + "id": 3650, + "initialPrice": 1199, + "discountTotal": 0, + "prices": [ + { + "price": 1199, + "quantity": 1 + } + ], + "createdAt": "2018-10-04 16:33:35", + "quantity": 1, + "status": "new", + "offer": { + "displayName": "Домашние Тапочки Розовый Рай", + "id": 20104, + "externalId": "208", + "xmlId": "469", + "name": "Домашние Тапочки Розовый Рай", + "article": "174-15-xx", + "vatRate": "none", + "unit": { + "code": "pc", + "name": "Штука", + "sym": "шт." + } + }, + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "fromApi": false, + "length": 0, + "width": 0, + "height": 0, + "shipmentStore": "main12", + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7087, + "createdAt": "2018-10-04 16:35:00", + "created": true, + "source": "user", + "user": { + "id": 19 + }, + "field": "status", + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2048, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2048, + "number": "2048C", + "orderType": "eshop-individual", + "orderMethod": "phone", + "countryIso": "RU", + "createdAt": "2018-10-04 16:35:00", + "statusUpdatedAt": "2018-10-04 16:35:00", + "summ": 0, + "totalSumm": 300, + "prepaySum": 300, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 16:35:00", + "firstName": "test", + "email": "test@test.test", + "call": false, + "expired": false, + "managerId": 19, + "customer": { + "type": "customer", + "id": 978, + "isContact": false, + "createdAt": "2018-10-03 11:28:01", + "managerId": 19, + "vip": false, + "bad": false, + "site": "moysklad", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 43345.42, + "totalSumm": 61632.6, + "averageSumm": 844.28, + "ordersCount": 73, + "costSumm": 18287.18, + "customFields": { + "moyskladexternalid": "2f7651a1-e8e1-11e8-9109-f8fc0005536e" + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 982, + "countryIso": "RU", + "region": "Москва город", + "regionId": 55, + "city": "Москва", + "cityId": 4995, + "cityType": "г.", + "street": "Мира", + "streetId": 1779257, + "streetType": "пр-кт.", + "building": "5", + "notes": "345645364356b45 t56 g655gb5 g", + "text": "пр-кт. Мира, д. 5, 345645364356b45 t56 g655gb5 g" + }, + "segments": [ + { + "id": 1, + "code": "moskva", + "name": "Москва", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 47, + "active": true + }, + { + "id": 3, + "code": "klienti-iz-gorodov-millionnikov", + "name": "Клиенты из городов-миллионников", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 64, + "active": true + }, + { + "id": 4, + "code": "rossiya", + "name": "Россия", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 206, + "active": true + }, + { + "id": 11, + "code": "davnie", + "name": "Давние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 935, + "active": true + }, + { + "id": 12, + "code": "bolshaya-summa-pokupok", + "name": "Большая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 4, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "svfds", + "lastName": "dsadavfdvsdfvd", + "patronymic": "vfdscdsvsddsa", + "email": "test@test.test", + "phones": [ + { + "number": "89099999999" + }, + { + "number": "532523454365436" + }, + { + "number": "890999999999" + }, + { + "number": "911" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "courier", + "cost": 300, + "netCost": 0, + "address": { + "id": 2048, + "countryIso": "RU" + } + }, + "site": "moysklad", + "status": "new", + "items": [ + { + "id": 3651, + "initialPrice": 0, + "discountTotal": 0, + "prices": [ + { + "price": 0, + "quantity": 1 + } + ], + "vatRate": "none", + "createdAt": "2018-10-04 16:35:00", + "quantity": 1, + "status": "new", + "offer": { + "displayName": "Рукола (Зеленая дважды)", + "id": 20274, + "externalId": "S1bbBvZ5h5f0N4hJbSK6B2#GpGvbiu6jwbbf7rku5XXU2", + "xmlId": "S1bbBvZ5h5f0N4hJbSK6B2#GpGvbiu6jwbbf7rku5XXU2", + "name": "Рукола (Зеленая дважды)", + "vatRate": "none", + "unit": { + "code": "796", + "name": "Штука", + "sym": "шт" + } + }, + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "fullPaidAt": "2018-10-04 00:00:00", + "payments": [ + { + "id": 1436, + "status": "paid", + "type": "cash", + "amount": 300, + "paidAt": "2018-10-04 00:00:00" + } + ], + "fromApi": false, + "length": 0, + "width": 0, + "height": 0, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7088, + "createdAt": "2018-10-04 16:35:15", + "source": "user", + "user": { + "id": 19 + }, + "field": "shipment_store", + "oldValue": null, + "newValue": { + "code": "food" + }, + "order": { + "id": 2048, + "managerId": 19, + "site": "moysklad", + "status": "new" + } + }, + { + "id": 7089, + "createdAt": "2018-10-04 16:36:27", + "created": true, + "source": "user", + "user": { + "id": 19 + }, + "field": "status", + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2049, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2049, + "number": "2049C", + "orderType": "eshop-individual", + "orderMethod": "phone", + "countryIso": "RU", + "createdAt": "2018-10-04 16:36:27", + "statusUpdatedAt": "2018-10-04 16:36:27", + "summ": 1099, + "totalSumm": 1099, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-04 16:36:27", + "firstName": "test", + "email": "test@test.test", + "call": false, + "expired": false, + "managerId": 19, + "customer": { + "type": "customer", + "id": 978, + "isContact": false, + "createdAt": "2018-10-03 11:28:01", + "managerId": 19, + "vip": false, + "bad": false, + "site": "moysklad", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 43345.42, + "totalSumm": 61632.6, + "averageSumm": 844.28, + "ordersCount": 73, + "costSumm": 18287.18, + "customFields": { + "moyskladexternalid": "2f7651a1-e8e1-11e8-9109-f8fc0005536e" + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 982, + "countryIso": "RU", + "region": "Москва город", + "regionId": 55, + "city": "Москва", + "cityId": 4995, + "cityType": "г.", + "street": "Мира", + "streetId": 1779257, + "streetType": "пр-кт.", + "building": "5", + "notes": "345645364356b45 t56 g655gb5 g", + "text": "пр-кт. Мира, д. 5, 345645364356b45 t56 g655gb5 g" + }, + "segments": [ + { + "id": 1, + "code": "moskva", + "name": "Москва", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 47, + "active": true + }, + { + "id": 3, + "code": "klienti-iz-gorodov-millionnikov", + "name": "Клиенты из городов-миллионников", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 64, + "active": true + }, + { + "id": 4, + "code": "rossiya", + "name": "Россия", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 206, + "active": true + }, + { + "id": 11, + "code": "davnie", + "name": "Давние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 935, + "active": true + }, + { + "id": 12, + "code": "bolshaya-summa-pokupok", + "name": "Большая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 4, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "svfds", + "lastName": "dsadavfdvsdfvd", + "patronymic": "vfdscdsvsddsa", + "email": "test@test.test", + "phones": [ + { + "number": "89099999999" + }, + { + "number": "532523454365436" + }, + { + "number": "890999999999" + }, + { + "number": "911" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "cost": 0, + "netCost": 0, + "address": { + "id": 2049, + "countryIso": "RU" + } + }, + "site": "BitrixMod", + "status": "new", + "items": [ + { + "id": 3652, + "initialPrice": 1099, + "discountTotal": 0, + "prices": [ + { + "price": 1099, + "quantity": 1 + } + ], + "createdAt": "2018-10-04 16:36:27", + "quantity": 1, + "status": "new", + "offer": { + "displayName": "Нижнее белье Интимный Вечер", + "id": 20186, + "externalId": "125", + "xmlId": "313", + "name": "Нижнее белье Интимный Вечер", + "article": "235-79-хх", + "vatRate": "none", + "unit": { + "code": "pc", + "name": "Штука", + "sym": "шт." + } + }, + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "fromApi": false, + "length": 0, + "width": 0, + "height": 0, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7090, + "createdAt": "2018-10-04 16:36:34", + "source": "user", + "user": { + "id": 19 + }, + "field": "status", + "oldValue": { + "code": "new" + }, + "newValue": { + "code": "send-to-assembling" + }, + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "send-to-assembling" + } + }, + { + "id": 7091, + "createdAt": "2018-10-04 16:36:38", + "source": "user", + "user": { + "id": 19 + }, + "field": "status", + "oldValue": { + "code": "new" + }, + "newValue": { + "code": "send-to-assembling" + }, + "order": { + "id": 2047, + "managerId": 19, + "site": "moysklad", + "status": "send-to-assembling" + } + }, + { + "id": 7092, + "createdAt": "2018-10-04 16:36:42", + "source": "user", + "user": { + "id": 19 + }, + "field": "status", + "oldValue": { + "code": "new" + }, + "newValue": { + "code": "send-to-assembling" + }, + "order": { + "id": 2048, + "managerId": 19, + "site": "moysklad", + "status": "send-to-assembling" + } + }, + { + "id": 7093, + "createdAt": "2018-10-04 16:36:46", + "source": "user", + "user": { + "id": 19 + }, + "field": "status", + "oldValue": { + "code": "new" + }, + "newValue": { + "code": "send-to-assembling" + }, + "order": { + "id": 2049, + "managerId": 19, + "site": "BitrixMod", + "status": "send-to-assembling" + } + }, + { + "id": 7094, + "createdAt": "2018-10-04 17:18:50", + "source": "api", + "field": "custom_moyskladexternalid", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "68f33e70-c7e0-11e8-9ff4-34e8000afb5a", + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "send-to-assembling" + } + }, + { + "id": 7095, + "createdAt": "2018-10-04 17:18:51", + "source": "api", + "field": "custom_moyskladexternalid", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "69662cb7-c7e0-11e8-9ff4-3150000ae83f", + "order": { + "id": 2048, + "managerId": 19, + "site": "moysklad", + "status": "send-to-assembling" + } + }, + { + "id": 7096, + "createdAt": "2018-10-05 11:06:46", + "source": "user", + "user": { + "id": 19 + }, + "field": "order_product.quantity", + "oldValue": 1, + "newValue": 2, + "order": { + "id": 2048, + "managerId": 19, + "site": "moysklad", + "status": "send-to-assembling" + }, + "item": { + "id": 3651, + "offer": { + "id": 20274, + "externalId": "S1bbBvZ5h5f0N4hJbSK6B2#GpGvbiu6jwbbf7rku5XXU2", + "xmlId": "S1bbBvZ5h5f0N4hJbSK6B2#GpGvbiu6jwbbf7rku5XXU2" + } + } + }, + { + "id": 7097, + "createdAt": "2018-10-05 11:06:46", + "source": "user", + "user": { + "id": 19 + }, + "field": "order_product.status", + "oldValue": { + "code": "new" + }, + "newValue": { + "code": "ready-for-assembly" + }, + "order": { + "id": 2048, + "managerId": 19, + "site": "moysklad", + "status": "send-to-assembling" + }, + "item": { + "id": 3651, + "offer": { + "id": 20274, + "externalId": "S1bbBvZ5h5f0N4hJbSK6B2#GpGvbiu6jwbbf7rku5XXU2", + "xmlId": "S1bbBvZ5h5f0N4hJbSK6B2#GpGvbiu6jwbbf7rku5XXU2" + } + } + }, + { + "id": 7098, + "createdAt": "2018-10-05 11:06:53", + "source": "user", + "user": { + "id": 19 + }, + "field": "status", + "oldValue": { + "code": "send-to-assembling" + }, + "newValue": { + "code": "assembling" + }, + "order": { + "id": 2048, + "managerId": 19, + "site": "moysklad", + "status": "assembling" + } + }, + { + "id": 7099, + "createdAt": "2018-10-05 11:12:22", + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": { + "code": "assembling" + }, + "newValue": { + "code": "cancel-other" + }, + "order": { + "id": 2048, + "managerId": 19, + "site": "moysklad", + "status": "cancel-other" + } + }, + { + "id": 7100, + "createdAt": "2018-10-05 11:13:42", + "source": "api", + "field": "shipment_date", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": "2018-10-05", + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "send-to-assembling" + } + }, + { + "id": 7101, + "createdAt": "2018-10-05 11:13:42", + "source": "api", + "field": "shipped", + "apiKey": { + "current": false + }, + "oldValue": false, + "newValue": true, + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "send-to-assembling" + } + }, + { + "id": 7102, + "createdAt": "2018-10-05 12:43:59", + "source": "user", + "user": { + "id": 19 + }, + "field": "full_paid_at", + "oldValue": null, + "newValue": "2018-10-05 12:43:59", + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "send-to-assembling" + } + }, + { + "id": 7103, + "createdAt": "2018-10-05 12:43:59", + "source": "user", + "user": { + "id": 19 + }, + "field": "payments.paid_at", + "oldValue": null, + "newValue": "2018-10-05 00:00:00", + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "send-to-assembling" + }, + "payment": { + "id": 1435, + "type": "bank-card" + } + }, + { + "id": 7104, + "createdAt": "2018-10-05 12:43:59", + "source": "user", + "user": { + "id": 19 + }, + "field": "payments.status", + "oldValue": { + "code": "not-paid" + }, + "newValue": { + "code": "paid" + }, + "order": { + "id": 2046, + "managerId": 19, + "site": "moysklad", + "status": "send-to-assembling" + }, + "payment": { + "id": 1435, + "type": "bank-card" + } + }, + { + "id": 7105, + "createdAt": "2018-10-05 13:51:11", + "created": true, + "source": "api", + "field": "status", + "apiKey": { + "current": false + }, + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2050, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2050, + "number": "2050A", + "externalId": "6", + "orderType": "eshop-individual", + "orderMethod": "shopping-cart", + "createdAt": "2018-10-05 13:51:10", + "statusUpdatedAt": "2018-10-05 13:51:11", + "summ": 60.17, + "totalSumm": 60.17, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2018-10-05 13:51:11", + "lastName": "admin", + "firstName": "Admin", + "phone": "9515120000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 982, + "externalId": "2", + "isContact": false, + "createdAt": "2018-10-05 13:49:07", + "managerId": 23, + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 983, + "index": "344092", + "countryIso": "", + "city": "Ростов", + "text": "test " + }, + "segments": [ + + ], + "firstName": "Admin", + "lastName": "admin", + "email": "test12@gmail.com", + "phones": [ + { + "number": "1245987" + }, + { + "number": "9515120000" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "cost": 0, + "netCost": 0, + "address": { + "id": 2050, + "index": "344092", + "countryIso": "", + "city": "Ростов", + "text": "test " + } + }, + "site": "presta", + "status": "new", + "items": [ + { + "id": 3653, + "initialPrice": 60.17310053999999, + "discountTotal": 0, + "prices": [ + { + "price": 60.17, + "quantity": 1 + } + ], + "createdAt": "2018-10-05 13:51:10", + "quantity": 1, + "status": "new", + "properties": { + "size": { + "name": "Size", + "value": "S", + "code": "size" + }, + "color": { + "name": "Color", + "value": "Beige", + "code": "color" + } + }, + "purchasePrice": 0 + } + ], + "payments": [ + { + "id": 1437, + "type": "bank-transfer", + "externalId": "6#GICOUOCSD", + "amount": 60.17 + } + ], + "fromApi": true, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7106, + "createdAt": "2018-10-05 13:51:47", + "source": "user", + "user": { + "id": 23 + }, + "field": "status", + "oldValue": { + "code": "new" + }, + "newValue": { + "code": "prepayed" + }, + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "prepayed" + } + }, + { + "id": 7107, + "createdAt": "2018-10-05 13:51:47", + "source": "user", + "user": { + "id": 23 + }, + "field": "country", + "oldValue": null, + "newValue": "RU", + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "prepayed" + } + }, + { + "id": 7108, + "createdAt": "2018-10-05 13:51:47", + "source": "user", + "user": { + "id": 23 + }, + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "prepayed" + } + }, + { + "id": 7109, + "createdAt": "2018-10-05 13:51:55", + "source": "user", + "user": { + "id": 23 + }, + "field": "full_paid_at", + "oldValue": null, + "newValue": "2018-10-05 13:51:55", + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "prepayed" + } + }, + { + "id": 7110, + "createdAt": "2018-10-05 13:51:55", + "source": "user", + "user": { + "id": 23 + }, + "field": "payments.paid_at", + "oldValue": null, + "newValue": "2018-10-05 00:00:00", + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "prepayed" + }, + "payment": { + "id": 1437, + "type": "bank-transfer", + "externalId": "6#GICOUOCSD" + } + }, + { + "id": 7111, + "createdAt": "2018-10-05 13:51:55", + "source": "user", + "user": { + "id": 23 + }, + "field": "payments.status", + "oldValue": null, + "newValue": { + "code": "paid" + }, + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "prepayed" + }, + "payment": { + "id": 1437, + "type": "bank-transfer", + "externalId": "6#GICOUOCSD" + } + }, + { + "id": 7112, + "createdAt": "2018-10-05 13:53:45", + "source": "user", + "user": { + "id": 23 + }, + "field": "delivery_type", + "oldValue": null, + "newValue": { + "code": "self-delivery" + }, + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "prepayed" + } + }, + { + "id": 7113, + "createdAt": "2018-10-05 13:53:45", + "source": "user", + "user": { + "id": 23 + }, + "field": "full_paid_at", + "oldValue": "2018-10-05 13:51:55", + "newValue": null, + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "prepayed" + } + }, + { + "id": 7114, + "createdAt": "2018-10-05 13:53:45", + "source": "user", + "user": { + "id": 23 + }, + "field": "payments.status", + "oldValue": { + "code": "paid" + }, + "newValue": { + "code": "not-paid" + }, + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "prepayed" + }, + "payment": { + "id": 1437, + "type": "bank-transfer", + "externalId": "6#GICOUOCSD" + } + }, + { + "id": 7115, + "createdAt": "2018-10-05 13:53:52", + "source": "user", + "user": { + "id": 23 + }, + "field": "full_paid_at", + "oldValue": null, + "newValue": "2018-10-05 00:00:00", + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "prepayed" + } + }, + { + "id": 7116, + "createdAt": "2018-10-05 13:53:52", + "source": "user", + "user": { + "id": 23 + }, + "field": "payments.status", + "oldValue": { + "code": "not-paid" + }, + "newValue": { + "code": "paid" + }, + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "prepayed" + }, + "payment": { + "id": 1437, + "type": "bank-transfer", + "externalId": "6#GICOUOCSD" + } + }, + { + "id": 7117, + "createdAt": "2018-10-05 13:54:18", + "source": "user", + "user": { + "id": 23 + }, + "field": "status", + "oldValue": { + "code": "prepayed" + }, + "newValue": { + "code": "complete" + }, + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "complete" + } + }, + { + "id": 7118, + "createdAt": "2018-10-05 13:54:18", + "source": "code", + "field": "order_product.status", + "oldValue": { + "code": "new" + }, + "newValue": { + "code": "saled" + }, + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "complete" + }, + "item": { + "id": 3653 + } + }, + { + "id": 7119, + "createdAt": "2018-10-05 13:55:18", + "source": "user", + "user": { + "id": 23 + }, + "field": "status", + "oldValue": { + "code": "complete" + }, + "newValue": { + "code": "cancel-other" + }, + "order": { + "id": 2050, + "externalId": "6", + "managerId": 23, + "site": "presta", + "status": "cancel-other" + } + }, + { + "id": 7120, + "createdAt": "2018-10-05 14:36:22", + "created": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "status", + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2051, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2051, + "number": "2051C", + "orderType": "eshop-individual", + "orderMethod": "phone", + "countryIso": "RU", + "createdAt": "2018-10-05 14:36:22", + "statusUpdatedAt": "2018-10-05 14:36:22", + "summ": 999, + "totalSumm": 1099, + "prepaySum": 0, + "purchaseSumm": 1, + "markDatetime": "2018-10-05 14:36:22", + "firstName": "test", + "call": false, + "expired": false, + "managerId": 23, + "customer": { + "type": "customer", + "id": 983, + "externalId": "3", + "isContact": false, + "createdAt": "2018-10-05 14:36:22", + "managerId": 23, + "vip": false, + "bad": false, + "site": "presta", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 0, + "totalSumm": 0, + "averageSumm": 0, + "ordersCount": 0, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 984, + "countryIso": "RU" + }, + "segments": [ + + ], + "firstName": "test", + "email": "", + "phones": [ + + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 100, + "netCost": 0, + "address": { + "id": 2051, + "countryIso": "RU" + } + }, + "site": "presta", + "status": "new", + "items": [ + { + "id": 3654, + "initialPrice": 0, + "discountTotal": 0, + "prices": [ + { + "price": 0, + "quantity": 1 + } + ], + "vatRate": "none", + "createdAt": "2018-10-05 14:36:22", + "quantity": 1, + "status": "new", + "offer": { + "displayName": "Рукола (Зеленая дважды)", + "id": 20274, + "externalId": "S1bbBvZ5h5f0N4hJbSK6B2#GpGvbiu6jwbbf7rku5XXU2", + "xmlId": "S1bbBvZ5h5f0N4hJbSK6B2#GpGvbiu6jwbbf7rku5XXU2", + "name": "Рукола (Зеленая дважды)", + "vatRate": "none", + "unit": { + "code": "796", + "name": "Штука", + "sym": "шт" + } + }, + "properties": [ + + ], + "purchasePrice": 0 + }, + { + "id": 3655, + "initialPrice": 999, + "discountTotal": 0, + "prices": [ + { + "price": 999, + "quantity": 1 + } + ], + "vatRate": "none", + "createdAt": "2018-10-05 14:36:22", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 1 + } + ], + "payments": [ + { + "id": 1438, + "status": "not-paid", + "type": "cash", + "amount": 0, + "paidAt": "2018-10-05 00:00:00" + } + ], + "fromApi": false, + "length": 0, + "width": 0, + "height": 0, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7121, + "createdAt": "2018-10-05 14:36:22", + "source": "code", + "field": "customer", + "oldValue": null, + "newValue": { + "id": 983, + "externalId": "3", + "site": "presta" + }, + "order": { + "id": 2051, + "managerId": 23, + "site": "presta", + "status": "new" + } + }, + { + "id": 7122, + "createdAt": "2018-10-05 15:04:36", + "created": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "status", + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2052, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2052, + "number": "2052C", + "orderType": "eshop-individual", + "orderMethod": "phone", + "countryIso": "RU", + "createdAt": "2018-10-05 15:04:36", + "statusUpdatedAt": "2018-10-05 15:04:36", + "summ": 21.25, + "totalSumm": 21.25, + "prepaySum": 21.25, + "purchaseSumm": 0, + "markDatetime": "2018-10-05 15:04:36", + "lastName": "test", + "firstName": "test", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "managerId": 23, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2052, + "countryIso": "RU", + "text": "ntncncn" + } + }, + "site": "presta", + "status": "new", + "items": [ + { + "id": 3656, + "initialPrice": 21.25, + "discountTotal": 0, + "prices": [ + { + "price": 21.25, + "quantity": 1 + } + ], + "vatRate": "none", + "createdAt": "2018-10-05 15:04:36", + "quantity": 1, + "status": "new", + "offer": { + "displayName": "Agustí Torelló Mata GR Barrica 2011", + "id": 19971, + "externalId": "88", + "name": "Agustí Torelló Mata GR Barrica 2011", + "vatRate": "none", + "unit": { + "code": "pc", + "name": "Штука", + "sym": "шт." + } + }, + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "fullPaidAt": "2018-10-05 00:00:00", + "payments": [ + { + "id": 1439, + "status": "paid", + "type": "cash", + "amount": 21.25, + "paidAt": "2018-10-05 00:00:00" + } + ], + "fromApi": false, + "length": 0, + "width": 0, + "height": 0, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7123, + "createdAt": "2018-10-05 15:12:02", + "created": true, + "source": "user", + "user": { + "id": 23 + }, + "field": "status", + "oldValue": null, + "newValue": { + "code": "new" + }, + "order": { + "slug": 2053, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 2053, + "number": "2053C", + "orderType": "eshop-individual", + "orderMethod": "phone", + "countryIso": "RU", + "createdAt": "2018-10-05 15:12:02", + "statusUpdatedAt": "2018-10-05 15:12:02", + "summ": 60.17, + "totalSumm": 60.17, + "prepaySum": 60.17, + "purchaseSumm": 15.3, + "markDatetime": "2018-10-05 15:12:02", + "lastName": "admin", + "firstName": "admin", + "phone": "7900000000", + "email": "admin@mail.ru", + "call": false, + "expired": false, + "managerId": 23, + "customer": { + "type": "customer", + "id": 971, + "externalId": "1", + "isContact": false, + "createdAt": "2019-03-21 00:29:10", + "managerId": 23, + "vip": false, + "bad": false, + "site": "BitrixMod", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 46065.7, + "totalSumm": 46065.7, + "averageSumm": 3290.41, + "ordersCount": 14, + "costSumm": 0, + "customFields": [ + + ], + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 981, + "index": "344092", + "countryIso": "GB", + "region": "Aberdeen", + "city": "test", + "text": "test " + }, + "segments": [ + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 13, + "code": "srednyaya-summa-pokupok", + "name": "Средняя сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 15, + "code": "bolshoy-ltv", + "name": "Большой LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 34, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 23, + "code": "regulyarniy-klient", + "name": "Регулярный клиент", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 5, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1290, + "active": true + } + ], + "firstName": "test", + "lastName": "test", + "email": "admin@mail.ru", + "phones": [ + { + "number": "7800553535" + }, + { + "number": "9515120022" + }, + { + "number": "984654846" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "code": "self-delivery", + "cost": 0, + "netCost": 0, + "address": { + "id": 2053, + "countryIso": "RU", + "text": "ntncncn" + } + }, + "site": "presta", + "status": "new", + "items": [ + { + "id": 3657, + "initialPrice": 60.17, + "discountTotal": 0, + "prices": [ + { + "price": 60.17, + "quantity": 1 + } + ], + "createdAt": "2018-10-05 15:12:02", + "quantity": 1, + "status": "new", + "properties": [ + + ], + "purchasePrice": 15.3 + } + ], + "fullPaidAt": "2018-10-05 00:00:00", + "payments": [ + { + "id": 1440, + "status": "paid", + "type": "cash", + "amount": 60.17, + "paidAt": "2018-10-05 00:00:00" + } + ], + "fromApi": false, + "length": 0, + "width": 0, + "height": 0, + "shipped": false, + "customFields": [ + + ] + } + }, + { + "id": 7124, + "createdAt": "2018-10-08 13:35:42", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2031, + "externalId": "14", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7125, + "createdAt": "2018-10-08 13:35:42", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2032, + "externalId": "15", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7126, + "createdAt": "2018-10-08 13:35:42", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2033, + "externalId": "16", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7127, + "createdAt": "2018-10-08 13:35:42", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2035, + "externalId": "17", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7128, + "createdAt": "2018-10-08 13:35:42", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2036, + "externalId": "18", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7129, + "createdAt": "2018-10-08 13:35:42", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2037, + "externalId": "21", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7130, + "createdAt": "2018-10-08 13:35:42", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2038, + "externalId": "22", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7131, + "createdAt": "2018-10-08 13:35:42", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2039, + "externalId": "23", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7132, + "createdAt": "2018-10-08 13:35:42", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2040, + "externalId": "25", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7133, + "createdAt": "2018-10-08 13:35:43", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2041, + "externalId": "26", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7134, + "createdAt": "2018-10-08 13:35:43", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2042, + "externalId": "27", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7135, + "createdAt": "2018-10-08 13:35:43", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2043, + "externalId": "28", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7136, + "createdAt": "2018-10-08 13:35:43", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2044, + "externalId": "29", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + }, + { + "id": 7137, + "createdAt": "2018-10-08 13:35:43", + "source": "code", + "field": "manager", + "oldValue": null, + "newValue": { + "id": 23 + }, + "order": { + "id": 2045, + "externalId": "30", + "managerId": 23, + "site": "BitrixMod", + "status": "new" + } + } + ], + "pagination": { + "limit": 100, + "totalCount": 29733, + "currentPage": 1, + "totalPageCount": 298 + } +} +EOF; + + $request = new OrdersHistoryRequest(); + $request->limit = 100; + $request->page = 1; + $request->filter = new OrderHistoryFilterV4Type(); + $request->filter->sinceId = 2691; + + $mock = static::createApiMockBuilder('orders/history'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->history($request); + + self::assertModelEqualsToResponse($json, $response, true); + } + + public function testLinksCreate(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $request = new OrdersLinksCreateRequest(); + $request->link = new SerializedOrderLink(); + $request->site = 'aliexpress'; + $request->link->orders = [ + SerializedEntityOrder::withNumber('8123522898559160'), + SerializedEntityOrder::withNumber('8123898472679160') + ]; + $request->link->comment = 'same client'; + + $mock = static::createApiMockBuilder('orders/links/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->linksCreate($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testLoyaltyApply(): void + { + $json = <<<'EOF' +{ + "success": true, + "order": { + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 10, + "privilegeType": "none", + "totalSumm": 9978, + "loyaltyAccount": { + "id": 156, + "amount": 901 + }, + "customer": { + "id": 4925, + "externalId": "1", + "personalDiscount": 0 + }, + "delivery": { + "cost": 0 + }, + "site": "bitrix-test", + "items": [ + { + "bonusesChargeTotal": 5, + "bonusesCreditTotal": 0, + "initialPrice": 4999, + "discountTotal": 10, + "prices": [ + { + "price": 4989, + "quantity": 1 + } + ], + "quantity": 1 + }, + { + "bonusesChargeTotal": 5, + "bonusesCreditTotal": 0, + "initialPrice": 4999, + "discountTotal": 10, + "prices": [ + { + "price": 4989, + "quantity": 1 + } + ], + "quantity": 1 + } + ] + } +} +EOF; + + $request = new OrdersLoyaltyApplyRequest(); + $request->site = 'bitrix-test'; + $request->order = SerializedEntityOrder::withNumber('7'); + $request->bonuses = 10; + + $mock = static::createApiMockBuilder('orders/loyalty/apply'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->loyaltyApply($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testPaymentsCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 1 +} +EOF; + + $request = new OrdersPaymentsCreateRequest(); + $request->payment = new SerializedPayment(); + $request->payment->type = 'bank-card'; + $request->payment->amount = 10000; + $request->payment->comment = 'Comment'; + $request->payment->order = SerializedEntityOrder::withNumber('8123522898559160'); + $request->site = 'aliexpress'; + + $mock = static::createApiMockBuilder('orders/payments/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->paymentsCreate($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testPaymentsDelete(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $mock = static::createApiMockBuilder('orders/payments/4562/delete'); + $mock->matchMethod(RequestMethod::POST) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->paymentsDelete(4562); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testPaymentsEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 1 +} +EOF; + + $request = new OrdersPaymentsEditRequest(); + $request->by = ByIdentifier::ID; + $request->payment = new SerializedPayment(); + $request->payment->comment = 'Comment'; + $request->site = 'aliexpress'; + + $mock = static::createApiMockBuilder('orders/payments/4562/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->paymentsEdit(4562, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testStatuses(): void + { + $json = <<<'EOF' +{ + "success": true, + "orders": [ + { + "id": 6722, + "externalId": "8123522898559160", + "status": "delivering", + "group": "delivery" + } + ] +} +EOF; + + $request = new OrdersStatusesRequest(); + $request->externalIds = ['8123522898559160']; + + $mock = static::createApiMockBuilder('orders/statuses'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->statuses($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testUpload(): void + { + $json = <<<'EOF' +{ + "success": true, + "uploadedOrders": [{ + "id": 7146, + "externalId": "external_id_7146" + }], + "orders": [{ + "slug": 7146, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 7146, + "externalId": "external_id_7146", + "number": "7146A", + "orderType": "test", + "orderMethod": "phone", + "privilegeType": "none", + "countryIso": "RU", + "createdAt": "2021-02-25 17:05:06", + "statusUpdatedAt": "2021-02-25 17:05:06", + "summ": 0, + "totalSumm": 0, + "prepaySum": 1000, + "purchaseSumm": 60, + "markDatetime": "2021-02-25 17:05:06", + "lastName": "User", + "firstName": "Test", + "patronymic": "Patronymic", + "phone": "89003005069", + "email": "testuser12345678901@example.com", + "call": false, + "expired": false, + "managerId": 28, + "customer": { + "type": "customer", + "id": 4924, + "isContact": false, + "createdAt": "2020-12-15 11:07:32", + "managerId": 28, + "vip": false, + "bad": false, + "site": "moysklad", + "contragent": { + "contragentType": "individual" + }, + "tags": [], + "marginSumm": -1157.1, + "totalSumm": 10224.4, + "averageSumm": 681.63, + "ordersCount": 15, + "costSumm": 11381.5, + "customFields": { + "galkatrue": true, + "moyskladexternalid": "03956a7e-3ead-11eb-0a80-017300081b64" + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 3515, + "index": "344001", + "countryIso": "RU", + "region": "Ростовская область", + "regionId": 73, + "city": "Ростов-на-Дону", + "cityId": 4298, + "cityType": "г.", + "street": "Пушкинская", + "streetId": 1583265, + "streetType": "ул.", + "building": "10", + "text": "ул. Пушкинская, д. 10" + }, + "segments": [ + { + "id": 3, + "code": "klienti-iz-gorodov-millionnikov", + "name": "Клиенты из городов-миллионников", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 64, + "active": true + }, + { + "id": 4, + "code": "rossiya", + "name": "Россия", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 206, + "active": true + }, + { + "id": 8, + "code": "rossiya-krome-msk", + "name": "Россия (кроме МСК)", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 174, + "active": true + }, + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 14, + "code": "malenkaya-summa-pokupok", + "name": "Маленькая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1053, + "active": true + }, + { + "id": 17, + "code": "nizkiy-ltv", + "name": "Низкий LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1008, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 29, + "code": "mugchini", + "name": "Мужчины", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 9, + "active": true + } + ], + "firstName": "Андрей", + "lastName": "М", + "patronymic": "В", + "sex": "male", + "presumableSex": "male", + "email": "testuser12345678901@example.com", + "phones": [ + { + "number": "89003005069" + }, + { + "number": "+79515151515" + } + ] + }, + "contact": { + "type": "customer", + "id": 4924, + "isContact": false, + "createdAt": "2020-12-15 11:07:32", + "managerId": 28, + "vip": false, + "bad": false, + "site": "moysklad", + "contragent": { + "contragentType": "individual" + }, + "tags": [], + "marginSumm": -1157.1, + "totalSumm": 10224.4, + "averageSumm": 681.63, + "ordersCount": 15, + "costSumm": 11381.5, + "customFields": { + "galkatrue": true, + "moyskladexternalid": "03956a7e-3ead-11eb-0a80-017300081b64" + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 3515, + "index": "344001", + "countryIso": "RU", + "region": "Ростовская область", + "regionId": 73, + "city": "Ростов-на-Дону", + "cityId": 4298, + "cityType": "г.", + "street": "Пушкинская", + "streetId": 1583265, + "streetType": "ул.", + "building": "10", + "text": "ул. Пушкинская, д. 10" + }, + "segments": [ + { + "id": 3, + "code": "klienti-iz-gorodov-millionnikov", + "name": "Клиенты из городов-миллионников", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 64, + "active": true + }, + { + "id": 4, + "code": "rossiya", + "name": "Россия", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 206, + "active": true + }, + { + "id": 8, + "code": "rossiya-krome-msk", + "name": "Россия (кроме МСК)", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 174, + "active": true + }, + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 81, + "active": true + }, + { + "id": 14, + "code": "malenkaya-summa-pokupok", + "name": "Маленькая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1053, + "active": true + }, + { + "id": 17, + "code": "nizkiy-ltv", + "name": "Низкий LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1008, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1246, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + }, + { + "id": 29, + "code": "mugchini", + "name": "Мужчины", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 9, + "active": true + } + ], + "firstName": "Андрей", + "lastName": "М", + "patronymic": "В", + "sex": "male", + "presumableSex": "male", + "email": "testuser12345678901@example.com", + "phones": [ + { + "number": "89003005069" + }, + { + "number": "+79515151515" + } + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "cost": 0, + "netCost": 0, + "address": { + "index": "344001", + "countryIso": "RU", + "region": "Ростовская область", + "city": "г. Ростов-на-Дону", + "street": "ул. Пушкинская", + "building": "10", + "text": "ул. Пушкинская, д. 10" + } + }, + "site": "moysklad", + "status": "assembling", + "statusComment": "Assembling order", + "items": [ + { + "markingCodes": [], + "id": 11308, + "priceType": { + "code": "base" + }, + "initialPrice": 0, + "discountTotal": 0, + "prices": [ + { + "price": 0, + "quantity": 1 + } + ], + "vatRate": "none", + "createdAt": "2021-02-25 17:05:06", + "quantity": 1, + "status": "new", + "offer": { + "displayName": "сбьорка№1445123", + "id": 61121, + "externalId": "tGunLo27jlPGmbA8BrHxY2", + "xmlId": "tGunLo27jlPGmbA8BrHxY2", + "name": "сбьорка№1445", + "article": "14451445-14451445", + "vatRate": "none", + "unit": { + "code": "796", + "name": "Штука", + "sym": "шт" + } + }, + "properties": [], + "purchasePrice": 60 + } + ], + "fullPaidAt": "2021-02-25 14:05:06", + "payments": [ + { + "id": 4554, + "status": "paid", + "type": "bank-card", + "amount": 1000, + "paidAt": "2021-02-25 14:05:06" + } + ], + "fromApi": true, + "weight": 1000, + "shipmentStore": "main12", + "shipmentDate": "2021-03-04", + "shipped": false, + "customFields": { + "galka": false, + "test_number": 0, + "otpravit_dozakaz": false + } + }] +} +EOF; + + $request = new OrdersUploadRequest(); + $order = new Order(); + $payment = new Payment(); + $delivery = new SerializedOrderDelivery(); + $deliveryAddress = new OrderDeliveryAddress(); + $offer = new Offer(); + $item = new OrderProduct(); + + $payment->type = 'bank-card'; + $payment->status = 'paid'; + $payment->amount = 1000; + $payment->paidAt = new DateTime(); + + $deliveryAddress->index = '344001'; + $deliveryAddress->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; + $deliveryAddress->region = 'Ростовская область'; + $deliveryAddress->city = 'г. Ростов-на-Дону'; + $deliveryAddress->street = 'ул. Пушкинская'; + $deliveryAddress->building = '10'; + + $delivery->address = $deliveryAddress; + $delivery->cost = 0; + $delivery->netCost = 0; + + $offer->name = 'Сборка №1445123'; + $offer->displayName = 'Сборка №1445123'; + $offer->xmlId = 'tGunLo27jlPGmbA8BrHxY2'; + $offer->article = '14451445-14451445'; + $offer->unit = new Unit('796', 'Штука', 'шт'); + + $item->offer = $offer; + $item->priceType = new PriceType('base'); + $item->quantity = 1; + $item->purchasePrice = 60; + + $order->delivery = $delivery; + $order->items = [$item]; + $order->payments = [$payment]; + $order->orderType = 'test'; + $order->orderMethod = 'phone'; + $order->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; + $order->firstName = 'Test'; + $order->lastName = 'User'; + $order->patronymic = 'Patronymic'; + $order->phone = '89003005069'; + $order->email = 'testuser12345678901@example.com'; + $order->managerId = 28; + $order->customer = SerializedRelationCustomer::withIdAndType( + 4924, + CustomerType::CUSTOMER + ); + $order->status = 'assembling'; + $order->statusComment = 'Assembling order'; + $order->weight = 1000; + $order->shipmentStore = 'main12'; + $order->shipmentDate = (new DateTime())->add(new DateInterval('P7D')); + $order->shipped = false; + $order->customFields = [ + "galka" => false, + "test_number" => 0, + "otpravit_dozakaz" => false, + ]; + + $request->site = 'moysklad'; + $request->orders = [$order]; + + $mock = static::createApiMockBuilder('orders/upload'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->upload($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testGet(): void + { + $json = <<<'EOF' +{ + "success": true, + "order": { + "slug": 6722, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 6722, + "number": "8123522898559160", + "externalId": "8123522898559160", + "orderMethod": "shopping-cart", + "countryIso": "RU", + "createdAt": "2020-12-29 17:35:08", + "statusUpdatedAt": "2020-12-31 15:09:31", + "summ": 1, + "totalSumm": 1, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2020-12-31 15:09:31", + "lastName": "Pavel", + "firstName": "Kovalenko", + "call": false, + "expired": false, + "customer": { + "type": "customer", + "id": 4976, + "externalId": "ru1067815391", + "isContact": false, + "createdAt": "2020-12-31 12:01:02", + "vip": false, + "bad": false, + "site": "aliexpress", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 2, + "totalSumm": 2, + "averageSumm": 1, + "ordersCount": 2, + "costSumm": 0, + "customFields": { + "galkatrue": true + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 3523, + "index": "344065", + "countryIso": "RU", + "region": "Rostovskaya oblast", + "city": "Rostov-Na-Donu", + "notes": "ул. 50-летия Ростсельмаша, 2/6" + }, + "segments": [ + { + "id": 4, + "code": "rossiya", + "name": "Россия", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 208, + "active": true + }, + { + "id": 8, + "code": "rossiya-krome-msk", + "name": "Россия (кроме МСК)", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 176, + "active": true + }, + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 82, + "active": true + }, + { + "id": 14, + "code": "malenkaya-summa-pokupok", + "name": "Маленькая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1054, + "active": true + }, + { + "id": 17, + "code": "nizkiy-ltv", + "name": "Низкий LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1008, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1249, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1296, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + } + ], + "firstName": "Kovalenko", + "lastName": "Pavel", + "phones": [ + + ] + }, + "contact": { + "type": "customer", + "id": 4976, + "externalId": "ru1067815391", + "isContact": false, + "createdAt": "2020-12-31 12:01:02", + "vip": false, + "bad": false, + "site": "aliexpress", + "contragent": { + "contragentType": "individual" + }, + "tags": [ + + ], + "marginSumm": 2, + "totalSumm": 2, + "averageSumm": 1, + "ordersCount": 2, + "costSumm": 0, + "customFields": { + "galkatrue": true + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 3523, + "index": "344065", + "countryIso": "RU", + "region": "Rostovskaya oblast", + "city": "Rostov-Na-Donu", + "notes": "ул. 50-летия Ростсельмаша, 2/6" + }, + "segments": [ + { + "id": 4, + "code": "rossiya", + "name": "Россия", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 208, + "active": true + }, + { + "id": 8, + "code": "rossiya-krome-msk", + "name": "Россия (кроме МСК)", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 176, + "active": true + }, + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 82, + "active": true + }, + { + "id": 14, + "code": "malenkaya-summa-pokupok", + "name": "Маленькая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1054, + "active": true + }, + { + "id": 17, + "code": "nizkiy-ltv", + "name": "Низкий LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1008, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1249, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1296, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1293, + "active": true + } + ], + "firstName": "Kovalenko", + "lastName": "Pavel", + "phones": [ + + ] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "cost": 0, + "netCost": 0, + "address": { + "index": "344065", + "countryIso": "RU", + "region": "Rostovskaya oblast", + "city": "Rostov-Na-Donu", + "notes": "ул. 50-летия Ростсельмаша, 2/6" + } + }, + "site": "aliexpress", + "status": "delivering", + "items": [ + { + "markingCodes": [ + + ], + "id": 10114, + "initialPrice": 1, + "discountTotal": 0, + "prices": [ + { + "price": 1, + "quantity": 1 + } + ], + "discounts": [ + { + "type": "round", + "amount": 0.5 + } + ], + "createdAt": "2020-12-29 17:35:08", + "quantity": 1, + "status": "new", + "offer": { + "displayName": "Test product/not for sale (do not order!)", + "id": 1867352, + "externalId": "1005001690749727", + "xmlId": "1680c0a6-c5a3-48a6-aa8d-864553a8add9", + "name": "Test product/not for sale (do not order!)", + "unit": { + "code": "pc", + "name": "Штука", + "sym": "шт." + } + }, + "properties": [ + + ], + "purchasePrice": 0 + } + ], + "payments": { + "4326": { + "id": 4326, + "type": "bank-card", + "externalId": "payment_8123522898559160", + "amount": 0, + "paidAt": "2020-12-29 17:35:12" + } + }, + "fromApi": true, + "shipmentStore": "main12", + "shipped": false, + "customFields": { + "galka": false, + "test_number": 0, + "otpravit_dozakaz": false + } + } +} +EOF; + + $request = new BySiteRequest(ByIdentifier::EXTERNAL_ID, 'aliexpress'); + + $mock = static::createApiMockBuilder('orders/8123522898559160'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->get('8123522898559160', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 6722, + "order": { + "slug": 6722, + "bonusesCreditTotal": 0, + "bonusesChargeTotal": 0, + "id": 6722, + "number": "8123522898559160", + "externalId": "8123522898559160", + "orderMethod": "shopping-cart", + "privilegeType": "none", + "countryIso": "RU", + "createdAt": "2020-12-29 17:35:08", + "statusUpdatedAt": "2020-12-31 15:09:31", + "summ": 1, + "totalSumm": 1, + "prepaySum": 0, + "purchaseSumm": 0, + "markDatetime": "2020-12-31 15:09:31", + "lastName": "Pavel", + "firstName": "Kovalenko", + "call": false, + "expired": false, + "managerComment": "Manager comment", + "customer": { + "type": "customer", + "id": 4976, + "externalId": "ru1067815391", + "isContact": false, + "createdAt": "2020-12-31 12:01:02", + "vip": false, + "bad": false, + "site": "aliexpress", + "contragent": { + "contragentType": "individual" + }, + "tags": [], + "marginSumm": 2, + "totalSumm": 2, + "averageSumm": 1, + "ordersCount": 2, + "costSumm": 0, + "customFields": { + "galkatrue": true + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 3523, + "index": "344065", + "countryIso": "RU", + "region": "Rostovskaya oblast", + "city": "Rostov-Na-Donu", + "notes": "ул. 50-летия Ростсельмаша, 2/6" + }, + "segments": [ + { + "id": 4, + "code": "rossiya", + "name": "Россия", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 211, + "active": true + }, + { + "id": 8, + "code": "rossiya-krome-msk", + "name": "Россия (кроме МСК)", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 179, + "active": true + }, + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 85, + "active": true + }, + { + "id": 14, + "code": "malenkaya-summa-pokupok", + "name": "Маленькая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1057, + "active": true + }, + { + "id": 17, + "code": "nizkiy-ltv", + "name": "Низкий LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1008, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1251, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1300, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1297, + "active": true + } + ], + "firstName": "Kovalenko", + "lastName": "Pavel", + "phones": [] + }, + "contact": { + "type": "customer", + "id": 4976, + "externalId": "ru1067815391", + "isContact": false, + "createdAt": "2020-12-31 12:01:02", + "vip": false, + "bad": false, + "site": "aliexpress", + "contragent": { + "contragentType": "individual" + }, + "tags": [], + "marginSumm": 2, + "totalSumm": 2, + "averageSumm": 1, + "ordersCount": 2, + "costSumm": 0, + "customFields": { + "galkatrue": true + }, + "personalDiscount": 0, + "cumulativeDiscount": 0, + "address": { + "id": 3523, + "index": "344065", + "countryIso": "RU", + "region": "Rostovskaya oblast", + "city": "Rostov-Na-Donu", + "notes": "ул. 50-летия Ростсельмаша, 2/6" + }, + "segments": [ + { + "id": 4, + "code": "rossiya", + "name": "Россия", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 211, + "active": true + }, + { + "id": 8, + "code": "rossiya-krome-msk", + "name": "Россия (кроме МСК)", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 179, + "active": true + }, + { + "id": 9, + "code": "nedavnie", + "name": "Недавние", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 85, + "active": true + }, + { + "id": 14, + "code": "malenkaya-summa-pokupok", + "name": "Маленькая сумма покупок", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1057, + "active": true + }, + { + "id": 17, + "code": "nizkiy-ltv", + "name": "Низкий LTV", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1008, + "active": true + }, + { + "id": 20, + "code": "nizkiy-sredniy-chek", + "name": "Низкий средний чек", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1251, + "active": true + }, + { + "id": 26, + "code": "bez-otmen", + "name": "Без отмен", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1300, + "active": true + }, + { + "id": 31, + "code": "pol-ne-ukazan", + "name": "Пол не указан", + "createdAt": "2018-09-04 16:35:59", + "isDynamic": true, + "customersCount": 1297, + "active": true + } + ], + "firstName": "Kovalenko", + "lastName": "Pavel", + "phones": [] + }, + "contragent": { + "contragentType": "individual" + }, + "delivery": { + "cost": 0, + "netCost": 0, + "address": { + "index": "344065", + "countryIso": "RU", + "region": "Rostovskaya oblast", + "city": "Rostov-Na-Donu", + "notes": "ул. 50-летия Ростсельмаша, 2/6" + } + }, + "site": "aliexpress", + "status": "delivering", + "items": [ + { + "markingCodes": [], + "id": 10114, + "initialPrice": 1, + "discountTotal": 0, + "prices": [ + { + "price": 1, + "quantity": 1 + } + ], + "discounts": [ + { + "type": "manual_product", + "amount": 1 + } + ], + "createdAt": "2020-12-29 17:35:08", + "quantity": 1, + "status": "new", + "offer": { + "displayName": "Test product/not for sale (do not order!)", + "id": 1867352, + "externalId": "1005001690749727", + "xmlId": "1680c0a6-c5a3-48a6-aa8d-864553a8add9", + "name": "Test product/not for sale (do not order!)", + "unit": { + "code": "pc", + "name": "Штука", + "sym": "шт." + } + }, + "properties": [], + "purchasePrice": 0 + } + ], + "payments": { + "4326": { + "id": 4326, + "type": "bank-card", + "externalId": "payment_8123522898559160", + "amount": 1, + "paidAt": "2020-12-29 17:35:12" + } + }, + "fromApi": true, + "shipmentStore": "main12", + "shipped": false, + "customFields": { + "galka": false, + "test_number": 0, + "otpravit_dozakaz": false + } + } +} +EOF; + + $order = new Order(); + $order->managerComment = 'Manager comment'; + + $request = new OrdersEditRequest(); + $request->by = ByIdentifier::EXTERNAL_ID; + $request->site = 'aliexpress'; + $request->order = $order; + + $mock = static::createApiMockBuilder('orders/8123522898559160/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->orders->edit('8123522898559160', $request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/PacksTest.php b/tests/src/ResourceGroup/PacksTest.php new file mode 100644 index 0000000..6a9d8af --- /dev/null +++ b/tests/src/ResourceGroup/PacksTest.php @@ -0,0 +1,1358 @@ +filter = new OrderProductPackFilter(); + $request->filter->ids = [143]; + + $mock = static::createApiMockBuilder('orders/packs'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->packs->list($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 1 +} +EOF; + + $pack = new OrderProductPack(); + $pack->itemId = 11235; + $pack->invoiceNumber = '1234567890'; + $pack->deliveryNoteNumber = '1234567890'; + $pack->shipmentDate = (new DateTime())->add(new DateInterval('P1D')); + $pack->store = 'main12'; + $pack->quantity = 1; + $pack->purchasePrice = 100; + + $request = new PacksCreateRequest($pack); + + $mock = static::createApiMockBuilder('orders/packs/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->packs->create($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testHistory(): void + { + $json = <<<'EOF' +{ + "success": true, + "generatedAt": "2021-03-02 14:30:54", + "history": [ + { + "id": 229, + "createdAt": "2020-02-06 13:59:41", + "created": true, + "field": "store", + "newValue": { + "code": "food" + }, + "pack": { + "id": 125, + "purchasePrice": 33.67, + "quantity": 1, + "store": { + "code": "food" + }, + "item": { + "id": 7642, + "order": { + "id": 5043 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 24 + } + }, + { + "id": 230, + "createdAt": "2020-02-06 15:01:16", + "created": true, + "field": "store", + "newValue": { + "code": "antisklad" + }, + "pack": { + "id": 126, + "quantity": 20, + "store": { + "code": "antisklad" + }, + "item": { + "id": 7642, + "order": { + "id": 5043 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "api" + }, + { + "id": 231, + "createdAt": "2020-02-12 17:29:38", + "deleted": true, + "field": "store", + "oldValue": { + "code": "food" + }, + "pack": { + "id": 125, + "item": { + "id": 7642, + "order": { + "id": 5043 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "code", + "user": { + "id": 24 + } + }, + { + "id": 232, + "createdAt": "2020-02-12 17:29:38", + "deleted": true, + "field": "store", + "oldValue": { + "code": "antisklad" + }, + "pack": { + "id": 126, + "item": { + "id": 7642, + "order": { + "id": 5043 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "code", + "user": { + "id": 24 + } + }, + { + "id": 233, + "createdAt": "2020-03-05 16:34:59", + "created": true, + "field": "store", + "newValue": { + "code": "food" + }, + "pack": { + "id": 127, + "purchasePrice": 33.67, + "quantity": 1, + "store": { + "code": "food" + }, + "item": { + "id": 8341, + "order": { + "id": 5463 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 19 + } + }, + { + "id": 234, + "createdAt": "2020-03-05 16:35:07", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 128, + "purchasePrice": 33.67, + "quantity": 1, + "store": { + "code": "main" + }, + "item": { + "id": 8343, + "order": { + "id": 5463 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 19 + } + }, + { + "id": 235, + "createdAt": "2020-03-05 16:35:13", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 129, + "quantity": 1, + "store": { + "code": "main" + }, + "item": { + "id": 8342, + "order": { + "id": 5463 + }, + "offer": { + "externalId": "test xml code", + "xmlId": "test xml code" + } + } + }, + "source": "user", + "user": { + "id": 19 + } + }, + { + "id": 236, + "createdAt": "2020-03-05 16:35:31", + "deleted": true, + "field": "store", + "oldValue": { + "code": "main12" + }, + "pack": { + "id": 128, + "item": { + "id": 8343, + "order": { + "id": 5463 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "code", + "user": { + "id": 19 + } + }, + { + "id": 237, + "createdAt": "2020-03-05 16:37:02", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 130, + "purchasePrice": 33.67, + "quantity": 1, + "store": { + "code": "main" + }, + "item": { + "id": 8343, + "order": { + "id": 5463 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 19 + } + }, + { + "id": 238, + "createdAt": "2020-03-05 16:37:02", + "deleted": true, + "field": "store", + "oldValue": { + "code": "main12" + }, + "pack": { + "id": 130, + "item": { + "id": 8343, + "order": { + "id": 5463 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "code", + "user": { + "id": 19 + } + }, + { + "id": 239, + "createdAt": "2020-03-05 16:37:15", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 131, + "purchasePrice": 33.67, + "quantity": 1, + "store": { + "code": "main" + }, + "item": { + "id": 8343, + "order": { + "id": 5463 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 19 + } + }, + { + "id": 240, + "createdAt": "2020-07-17 19:18:52", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 132, + "quantity": 2, + "store": { + "code": "main" + }, + "item": { + "id": 9294, + "order": { + "id": 6093 + }, + "offer": { + "externalId": "212", + "xmlId": "477" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 241, + "createdAt": "2020-07-17 20:34:53", + "deleted": true, + "field": "store", + "oldValue": { + "code": "main12" + }, + "pack": { + "id": 132, + "item": { + "id": 9294, + "order": { + "id": 6093 + }, + "offer": { + "externalId": "212", + "xmlId": "477" + } + } + }, + "source": "code", + "user": { + "id": 28 + } + }, + { + "id": 242, + "createdAt": "2020-07-18 18:13:15", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 133, + "quantity": 2, + "store": { + "code": "main" + }, + "item": { + "id": 9296, + "order": { + "id": 6097 + }, + "offer": { + "externalId": "212", + "xmlId": "477" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 243, + "createdAt": "2020-07-18 18:17:03", + "deleted": true, + "field": "store", + "oldValue": { + "code": "main12" + }, + "pack": { + "id": 133, + "item": { + "id": 9296, + "order": { + "id": 6097 + }, + "offer": { + "externalId": "212", + "xmlId": "477" + } + } + }, + "source": "code", + "user": { + "id": 28 + } + }, + { + "id": 244, + "createdAt": "2020-07-18 18:19:32", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 134, + "quantity": 1, + "store": { + "code": "main" + }, + "item": { + "id": 9298, + "order": { + "id": 6098 + }, + "offer": { + "externalId": "214", + "xmlId": "480" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 245, + "createdAt": "2020-07-19 12:49:05", + "deleted": true, + "field": "store", + "oldValue": { + "code": "main12" + }, + "pack": { + "id": 134, + "item": { + "id": 9298, + "order": { + "id": 6098 + }, + "offer": { + "externalId": "214", + "xmlId": "480" + } + } + }, + "source": "code", + "user": { + "id": 28 + } + }, + { + "id": 246, + "createdAt": "2020-08-25 17:18:40", + "created": true, + "field": "store", + "newValue": { + "code": "weiser_store" + }, + "pack": { + "id": 135, + "purchasePrice": 4000, + "quantity": 1, + "store": { + "code": "weiser_store" + }, + "item": { + "id": 9435, + "order": { + "id": 6225 + }, + "offer": { + "externalId": "77317025-132370645" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 247, + "createdAt": "2020-08-25 17:21:57", + "field": "purchasePrice", + "oldValue": 4000, + "newValue": 0, + "pack": { + "id": 135, + "item": { + "id": 9435, + "order": { + "id": 6225 + }, + "offer": { + "externalId": "77317025-132370645" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 248, + "createdAt": "2020-08-25 17:24:30", + "field": "purchasePrice", + "oldValue": 0, + "newValue": 4000, + "pack": { + "id": 135, + "item": { + "id": 9435, + "order": { + "id": 6225 + }, + "offer": { + "externalId": "77317025-132370645" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 249, + "createdAt": "2020-08-25 17:24:30", + "field": "quantity", + "oldValue": 1, + "newValue": 44, + "pack": { + "id": 135, + "item": { + "id": 9435, + "order": { + "id": 6225 + }, + "offer": { + "externalId": "77317025-132370645" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 250, + "createdAt": "2020-08-25 17:47:18", + "deleted": true, + "field": "store", + "oldValue": { + "code": "weiser_store" + }, + "pack": { + "id": 135, + "item": { + "id": 9435, + "order": { + "id": 6225 + }, + "offer": { + "externalId": "77317025-132370645" + } + } + }, + "source": "code", + "user": { + "id": 28 + } + }, + { + "id": 251, + "createdAt": "2020-12-14 11:25:40", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 136, + "purchasePrice": 2053.04, + "quantity": 2, + "store": { + "code": "main" + }, + "item": { + "id": 9932, + "order": { + "id": 6557 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 252, + "createdAt": "2020-12-14 11:39:58", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 137, + "purchasePrice": 2053.04, + "quantity": 1, + "store": { + "code": "main" + }, + "item": { + "id": 9947, + "order": { + "id": 6572 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 253, + "createdAt": "2020-12-15 11:34:22", + "created": true, + "field": "store", + "newValue": { + "code": "food" + }, + "pack": { + "id": 138, + "purchasePrice": 2053.04, + "quantity": 2, + "store": { + "code": "food" + }, + "item": { + "id": 9960, + "order": { + "id": 6583 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 254, + "createdAt": "2020-12-15 11:34:29", + "deleted": true, + "field": "store", + "oldValue": { + "code": "food" + }, + "pack": { + "id": 138, + "item": { + "id": 9960, + "order": { + "id": 6583 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 255, + "createdAt": "2020-12-15 11:34:29", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 139, + "purchasePrice": 2053.04, + "quantity": 2, + "store": { + "code": "main" + }, + "item": { + "id": 9960, + "order": { + "id": 6583 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 256, + "createdAt": "2020-12-15 15:18:39", + "deleted": true, + "field": "store", + "oldValue": { + "code": "main12" + }, + "pack": { + "id": 139, + "item": { + "id": 9960, + "order": { + "id": 6583 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "code", + "user": { + "id": 28 + } + }, + { + "id": 257, + "createdAt": "2020-12-15 15:19:57", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 140, + "purchasePrice": 2103.69, + "quantity": 1, + "store": { + "code": "main" + }, + "item": { + "id": 9961, + "order": { + "id": 6584 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 258, + "createdAt": "2020-12-15 18:15:12", + "deleted": true, + "field": "store", + "oldValue": { + "code": "main12" + }, + "pack": { + "id": 140, + "item": { + "id": 9961, + "order": { + "id": 6584 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "code", + "user": { + "id": 28 + } + }, + { + "id": 259, + "createdAt": "2020-12-16 09:08:20", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 141, + "purchasePrice": 2103.69, + "quantity": 1, + "store": { + "code": "main" + }, + "item": { + "id": 9963, + "order": { + "id": 6586 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 28 + } + }, + { + "id": 260, + "createdAt": "2020-12-29 16:50:41", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 142, + "purchasePrice": 4000, + "quantity": 1, + "store": { + "code": "main" + }, + "item": { + "id": 10057, + "order": { + "id": 6690 + }, + "offer": { + "externalId": "77317025-132370645" + } + } + }, + "source": "user", + "user": { + "id": 29 + } + }, + { + "id": 261, + "createdAt": "2020-12-29 16:53:18", + "field": "shipmentDate", + "newValue": "2020-12-29 00:00:00", + "pack": { + "id": 142, + "item": { + "id": 10057, + "order": { + "id": 6690 + }, + "offer": { + "externalId": "77317025-132370645" + } + } + }, + "source": "user", + "user": { + "id": 29 + } + }, + { + "id": 262, + "createdAt": "2020-12-29 16:53:43", + "deleted": true, + "field": "store", + "oldValue": { + "code": "main12" + }, + "pack": { + "id": 142, + "item": { + "id": 10057, + "order": { + "id": 6690 + }, + "offer": { + "externalId": "77317025-132370645" + } + } + }, + "source": "code", + "user": { + "id": 29 + } + }, + { + "id": 263, + "createdAt": "2021-02-11 16:34:47", + "created": true, + "field": "store", + "newValue": { + "code": "main12" + }, + "pack": { + "id": 143, + "quantity": 1, + "store": { + "code": "main1" + }, + "item": { + "id": 11235, + "order": { + "id": 7088 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 29 + } + }, + { + "id": 264, + "createdAt": "2021-02-11 16:38:12", + "field": "shipmentDate", + "newValue": "2021-02-11 00:00:00", + "pack": { + "id": 143, + "item": { + "id": 11235, + "order": { + "id": 7088 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 29 + } + }, + { + "id": 265, + "createdAt": "2021-02-11 17:11:07", + "field": "shipmentDate", + "oldValue": "2021-02-11 00:00:00", + "newValue": "2021-02-10 00:00:00", + "pack": { + "id": 143, + "item": { + "id": 11235, + "order": { + "id": 7088 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "user", + "user": { + "id": 29 + } + }, + { + "id": 266, + "createdAt": "2021-03-02 14:25:53", + "field": "purchasePrice", + "oldValue": 0, + "newValue": 100, + "pack": { + "id": 143, + "item": { + "id": 11235, + "order": { + "id": 7088 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "api" + }, + { + "id": 267, + "createdAt": "2021-03-02 14:25:53", + "field": "shipmentDate", + "oldValue": "2021-02-10 00:00:00", + "newValue": "2021-03-03 00:00:00", + "pack": { + "id": 143, + "item": { + "id": 11235, + "order": { + "id": 7088 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "api" + }, + { + "id": 268, + "createdAt": "2021-03-02 14:25:53", + "field": "invoiceNumber", + "newValue": "1234567890", + "pack": { + "id": 143, + "item": { + "id": 11235, + "order": { + "id": 7088 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "api" + }, + { + "id": 269, + "createdAt": "2021-03-02 14:25:53", + "field": "deliveryNoteNumber", + "newValue": "1234567890", + "pack": { + "id": 143, + "item": { + "id": 11235, + "order": { + "id": 7088 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + } + }, + "source": "api" + } + ], + "pagination": { + "limit": 100, + "totalCount": 41, + "currentPage": 1, + "totalPageCount": 1 + } +} +EOF; + + $request = new PacksHistoryRequest(); + $request->filter = new OrderProductPackHistoryFilterType(); + $request->filter->startDate = DateTimeTransformer::create('2020-01-01 00:00:00'); + + $mock = static::createApiMockBuilder('orders/packs/history'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->packs->history($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testGet(): void + { + $json = <<<'EOF' +{ + "success": true, + "pack": { + "id": 143, + "purchasePrice": 100, + "quantity": 1, + "unit": { + "code": "796", + "name": "Штука", + "sym": "шт" + }, + "store": "main12", + "item": { + "id": 11235, + "externalIds": [ + { + + } + ], + "order": { + "id": 7088 + }, + "offer": { + "externalId": "0jpQ-POMiUzGGZw0mvZPi3", + "xmlId": "0jpQ-POMiUzGGZw0mvZPi3" + } + }, + "shipmentDate": "2021-03-03", + "invoiceNumber": "1234567890", + "deliveryNoteNumber": "1234567890" + } +} +EOF; + + $mock = static::createApiMockBuilder('orders/packs/143'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->packs->get(143); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testEdit(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 1 +} +EOF; + + $pack = new OrderProductPack(); + $pack->shipmentDate = (new DateTime())->add(new DateInterval('P1D')); + + $request = new PacksCreateRequest($pack); + + $mock = static::createApiMockBuilder('orders/packs/143/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->packs->edit(143, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testDelete(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $mock = static::createApiMockBuilder('orders/packs/143/delete'); + $mock->matchMethod(RequestMethod::POST) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->packs->delete(143); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/PaymentsTest.php b/tests/src/ResourceGroup/PaymentsTest.php new file mode 100644 index 0000000..8cd2d41 --- /dev/null +++ b/tests/src/ResourceGroup/PaymentsTest.php @@ -0,0 +1,116 @@ +invoiceUuid = '5b1b2e9d-b7f1-48f4-acb9-4bfce04b30cf'; + $checkRequest->currency = Currency::RUB; + $checkRequest->amount = .5; + + $request = new PaymentCheckRequest($checkRequest); + + $mock = static::createApiMockBuilder('payment/check'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->payments->check($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCreateInvoice(): void + { + $json = <<<'EOF' +{ + "success": true, + "result": { + "link": "https:\/\/example.com" + } +} +EOF; + + $invoiceRequest = new ApiCreateInvoiceRequest(); + $invoiceRequest->paymentId = 4571; + $invoiceRequest->returnUrl = 'https://example.com'; + + $request = new PaymentCreateInvoiceRequest($invoiceRequest); + + $mock = static::createApiMockBuilder('payment/create-invoice'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->payments->createInvoice($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testUpdateInvoice(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $invoiceRequest = new ApiUpdateInvoiceRequest(); + $invoiceRequest->paymentId = 'd6458333-fff3-4fd0-9b23-4e6344451f8e'; + $invoiceRequest->invoiceUuid = '5b1b2e9d-b7f1-48f4-acb9-4bfce04b30cf'; + $invoiceRequest->invoiceUrl = 'https://example.com/newUrl'; + + $request = new PaymentUpdateInvoiceRequest($invoiceRequest); + + $mock = static::createApiMockBuilder('payment/update-invoice'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(static::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->payments->updateInvoice($request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/ReferencesTest.php b/tests/src/ResourceGroup/ReferencesTest.php new file mode 100644 index 0000000..3315bdf --- /dev/null +++ b/tests/src/ResourceGroup/ReferencesTest.php @@ -0,0 +1,8503 @@ +matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->costGroups(); + + self::assertModelEqualsToResponse($json, $response); + } + public function testCostGroupsEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new CostGroup(); + $entity->name = 'Комиссии'; + $entity->ordering = 60; + $entity->active = true; + + $request = new CostGroupsEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/cost-groups/commission/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->costGroupsEdit('commission', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCostItems(): void + { + $json = <<<'EOF' +{ + "success": true, + "costItems": [ + { + "code": "products-purchase-price", + "name": "Закупочная стоимость товаров", + "group": "product-cost", + "ordering": 990, + "active": true, + "appliesToOrders": true, + "type": "var", + "appliesToUsers": false + }, + { + "code": "delivery-cost", + "name": "Стоимость доставки", + "group": "delivery", + "ordering": 991, + "active": true, + "appliesToOrders": true, + "type": "var", + "appliesToUsers": false + }, + { + "code": "office-expenses", + "name": "Офисные расходы", + "group": "administrative", + "ordering": 10, + "active": true, + "appliesToOrders": false, + "type": "const", + "appliesToUsers": false + }, + { + "code": "payroll-fund", + "name": "ФОТ", + "group": "administrative", + "ordering": 20, + "active": true, + "appliesToOrders": false, + "type": "const", + "appliesToUsers": true + }, + { + "code": "employee-bonus", + "name": "Бонусы сотрудникам", + "group": "administrative", + "ordering": 30, + "active": true, + "appliesToOrders": true, + "type": "var", + "appliesToUsers": true + }, + { + "code": "payment-systems-commission", + "name": "Комиссии платёжных систем", + "group": "commission", + "ordering": 40, + "active": true, + "appliesToOrders": true, + "type": "var", + "appliesToUsers": false + }, + { + "code": "commission-for-warehouse-services", + "name": "Комиссии за услуги склада", + "group": "assembling", + "ordering": 50, + "active": true, + "appliesToOrders": true, + "type": "var", + "appliesToUsers": false + }, + { + "code": "product-moving", + "name": "Забор товара от поставщика", + "group": "assembling", + "ordering": 60, + "active": true, + "appliesToOrders": true, + "type": "var", + "appliesToUsers": false + }, + { + "code": "packaging", + "name": "Упаковка", + "group": "assembling", + "ordering": 70, + "active": true, + "appliesToOrders": true, + "type": "var", + "appliesToUsers": false + }, + { + "code": "warehousing-services", + "name": "Складское обслуживание", + "group": "assembling", + "ordering": 80, + "active": true, + "appliesToOrders": true, + "type": "const", + "appliesToUsers": false + }, + { + "code": "seo", + "name": "SEO", + "group": "attraction", + "ordering": 90, + "active": true, + "appliesToOrders": false, + "type": "const", + "appliesToUsers": false + }, + { + "code": "context-ad", + "name": "Контекстная реклама", + "group": "attraction", + "ordering": 100, + "active": true, + "appliesToOrders": false, + "type": "const", + "appliesToUsers": false + }, + { + "code": "banner-ad", + "name": "Баннерная реклама", + "group": "attraction", + "ordering": 110, + "active": true, + "appliesToOrders": false, + "type": "const", + "appliesToUsers": false + }, + { + "code": "emailing", + "name": "Email-рассылки", + "group": "attraction", + "ordering": 120, + "active": true, + "appliesToOrders": false, + "type": "const", + "appliesToUsers": false + }, + { + "code": "partnership", + "name": "Партнерская программа", + "group": "attraction", + "ordering": 130, + "active": true, + "appliesToOrders": false, + "type": "const", + "appliesToUsers": false + }, + { + "code": "referrals", + "name": "Рефералы", + "group": "attraction", + "ordering": 140, + "active": true, + "appliesToOrders": false, + "type": "const", + "appliesToUsers": false + }, + { + "code": "social-media-ad", + "name": "Реклама в соцсетях", + "group": "attraction", + "ordering": 150, + "active": true, + "appliesToOrders": false, + "type": "const", + "appliesToUsers": false + } + ] +} +EOF; + + $mock = static::createApiMockBuilder('reference/cost-items'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->costItems(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCostItemsEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new CostItem(); + $entity->name = "Test item"; + $entity->group = "product-cost"; + $entity->ordering = 990; + $entity->active = true; + $entity->appliesToOrders = true; + $entity->type = "var"; + $entity->appliesToUsers = false; + + $request = new CostItemsEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/cost-items/test-item/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->costItemsEdit('test-item', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCountries(): void + { + $json = <<<'EOF' +{ + "success": true, + "countriesIso": [ + "RU", + "UA", + "BY", + "KZ", + "AZ", + "HU", + "PL", + "GB", + "US", + "FR", + "ES" + ] +} +EOF; + + $mock = static::createApiMockBuilder('reference/countries'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->countries(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCouriers(): void + { + $json = <<<'EOF' +{ + "success": true, + "couriers": [ + { + "id": 3, + "firstName": "Артур", + "lastName": "Пирожков", + "active": true, + "phone": { + "number": "88005553535" + } + } + ] +} +EOF; + + $mock = static::createApiMockBuilder('reference/couriers'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->couriers(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCouriersCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 1 +} +EOF; + + $entity = new Courier(); + $entity->firstName = 'Tester'; + $entity->lastName = 'Tester'; + $entity->phone = new CourierPhone('88005553125'); + + $request = new CouriersCreateRequest($entity); + + $mock = static::createApiMockBuilder('reference/couriers/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->couriersCreate($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCouriersEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new Courier(); + $entity->firstName = 'Tester'; + $entity->lastName = 'Courier'; + $entity->phone = new CourierPhone('88005553126'); + + $request = new CouriersCreateRequest($entity); + + $mock = static::createApiMockBuilder('reference/couriers/4/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->couriersEdit(4, $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testDeliveryServices(): void + { + $json = <<<'EOF' +{ + "success": true, + "deliveryServices": { + "1": { + "name": "Единая ставка", + "code": "1", + "active": true + }, + "2": { + "name": "Flat rate", + "code": "2", + "active": true + }, + "3": { + "name": "Free shipping", + "code": "3", + "active": true + }, + "avia": { + "name": "Воздушный транспорт", + "code": "avia", + "active": true + }, + "land": { + "name": "Наземный транспорт", + "code": "land", + "active": true + }, + "pvz-out2": { + "name": "pvpHern", + "code": "pvz-out2", + "active": true + }, + "pvz-out3": { + "name": "pvpHern", + "code": "pvz-out3", + "active": true + }, + "dict-deliveryservices-1571123786": { + "name": "Bbbdict-deliveryservices-1571123786", + "code": "dict-deliveryservices-1571123786", + "active": false + }, + "dict-deliveryservices-1571123849": { + "name": "Bbbdict-deliveryservices-1571123849", + "code": "dict-deliveryservices-1571123849", + "active": false + }, + "dict-deliveryservices-1571124851": { + "name": "Bbbdict-deliveryservices-1571124851", + "code": "dict-deliveryservices-1571124851", + "active": false + }, + "dict-deliveryservices-1571124915": { + "name": "Bbbdict-deliveryservices-1571124915", + "code": "dict-deliveryservices-1571124915", + "active": false + }, + "dict-deliveryservices-1571134088": { + "name": "Bbbdict-deliveryservices-1571134088", + "code": "dict-deliveryservices-1571134088", + "active": false + }, + "dict-deliveryservices-1571134205": { + "name": "Bbbdict-deliveryservices-1571134205", + "code": "dict-deliveryservices-1571134205", + "active": false + }, + "dict-deliveryservices-1575878847": { + "name": "Bbbdict-deliveryservices-1575878847", + "code": "dict-deliveryservices-1575878847", + "active": false + }, + "dict-deliveryservices-1575878957": { + "name": "Bbbdict-deliveryservices-1575878957", + "code": "dict-deliveryservices-1575878957", + "active": false + }, + "dict-deliveryservices-1581413621": { + "name": "Bbbdict-deliveryservices-1581413621", + "code": "dict-deliveryservices-1581413621", + "active": false + }, + "dict-deliveryservices-1581413624": { + "name": "Bbbdict-deliveryservices-1581413624", + "code": "dict-deliveryservices-1581413624", + "active": false + }, + "dict-deliveryservices-1581413629": { + "name": "Bbbdict-deliveryservices-1581413629", + "code": "dict-deliveryservices-1581413629", + "active": false + }, + "dict-deliveryservices-1581413633": { + "name": "Bbbdict-deliveryservices-1581413633", + "code": "dict-deliveryservices-1581413633", + "active": false + }, + "dict-deliveryservices-1581413756": { + "name": "Bbbdict-deliveryservices-1581413756", + "code": "dict-deliveryservices-1581413756", + "active": false + }, + "dict-deliveryservices-1581413762": { + "name": "Bbbdict-deliveryservices-1581413762", + "code": "dict-deliveryservices-1581413762", + "active": false + }, + "dict-deliveryservices-1581413765": { + "name": "Bbbdict-deliveryservices-1581413765", + "code": "dict-deliveryservices-1581413765", + "active": false + }, + "dict-deliveryservices-1581413773": { + "name": "Bbbdict-deliveryservices-1581413773", + "code": "dict-deliveryservices-1581413773", + "active": false + } + } +} +EOF; + + $mock = static::createApiMockBuilder('reference/delivery-services'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->deliveryServices(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testDeliveryServicesEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new DeliveryService(); + $entity->name = 'dict-deliveryservices-1571123786'; + $entity->active = false; + + $request = new DeliveryServicesEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/delivery-services/dict-deliveryservices-1571123786/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->deliveryServicesEdit('dict-deliveryservices-1571123786', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testDeliveryTypes(): void + { + $json = <<<'EOF' +{ + "success": true, + "deliveryTypes": { + "2": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка курьером", + "code": "2", + "active": true, + "defaultCost": 500, + "defaultNetCost": 0, + "description": "Доставка осуществляется в течение дня в удобное для вас время.", + "paymentTypes": [ + "7", + "bonuses-sl", + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "3": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Самовывоз", + "code": "3", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Вы можете самостоятельно забрать заказ из нашего магазина.", + "paymentTypes": [ + "12", + "13", + "14", + "15", + "16", + "test-payment-integration" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "8": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Самовывоз", + "code": "8", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "13", + "14", + "16", + "test-payment-integration" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "9": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Укрпочтой", + "code": "9", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "13", + "14", + "16" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "10": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Вариант доставки не определен", + "code": "10", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "11": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Курьером по Киеву", + "code": "11", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "13", + "15" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "12": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Автолюкс", + "code": "12", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "13": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Интайм", + "code": "13", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "15": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Деливери", + "code": "15", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "16": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Почта России", + "code": "16", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка почтой (расчёт на основании табличных данных)", + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "e-money" + ], + "deliveryServices": [ + "avia", + "land" + ], + "defaultForCrm": false + }, + "583103": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Самовывоз InSales Тест", + "code": "583103", + "active": true, + "defaultCost": 100, + "defaultNetCost": 0, + "description": "", + "paymentTypes": [ + "442099", + "442100", + "555159", + "555161", + "776106", + "818241", + "897285", + "423361" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "651157": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "EMS Почта России InSales", + "code": "651157", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "", + "paymentTypes": [ + "423361", + "442099", + "442100", + "555159", + "555161", + "818241", + "897285" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "651158": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "СПСР Экспресс InSales", + "code": "651158", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "", + "paymentTypes": [ + "423361", + "442099", + "442100", + "555159", + "555161", + "818241", + "897285" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "1344511": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Название", + "code": "1344511", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "", + "paymentTypes": [ + "818241", + "897285" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "1832810": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка в точки самовывоза", + "code": "1832810", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "", + "paymentTypes": [ + "423361", + "442099", + "442100", + "555159", + "555161", + "776106", + "818241", + "897285" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "1939924": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Test", + "code": "1939924", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "", + "paymentTypes": [ + "897285" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "1960337": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Тест фиксированной стоимости", + "code": "1960337", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "", + "paymentTypes": [ + "423361", + "442099", + "442100", + "555159", + "555161", + "776106", + "818241", + "897285" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "1993015": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Тест1", + "code": "1993015", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "", + "paymentTypes": [ + "423361", + "442099", + "442100", + "555159", + "555161", + "776106", + "818241", + "897285" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "1994098": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Тест интеграционной доставки", + "code": "1994098", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "", + "paymentTypes": [ + "423361", + "442099", + "442100", + "555159", + "555161", + "776106", + "818241", + "897285" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "2014666": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Курьером", + "code": "2014666", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "", + "paymentTypes": [ + "423361", + "442099", + "442100", + "555159", + "555161", + "776106", + "818241", + "897285" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "2058866": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Самовывоз из точки \"Точка продаж\"", + "code": "2058866", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "", + "paymentTypes": [ + "555161" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "2503367": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "67883", + "code": "2503367", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "

создать заказ с этой доставкой по умолчанию

", + "paymentTypes": [ + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "423361", + "442100" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "2508998": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "New: Доставка в точки самовывоза", + "code": "2508998", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "", + "paymentTypes": [ + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "423361", + "442100" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "iml-test": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "IML-test", + "code": "iml-test", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "iml-1-5bbdbf91e3004", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "iml-1": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "iml-1", + "code": "iml-1", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "iml-7", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "boxberry-old": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Boxberry old", + "code": "boxberry-old", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "boxberry-1-5bbdc1cb90c32", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "hernya": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "hernya", + "code": "hernya", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "hernya", + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "courierist-1", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "russian-post-online-analitika": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Почта России (Аналитика Онлайн)", + "code": "russian-post-online-analitika", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "cash", + "bank-card", + "bank-transfer", + "credit", + "e-money" + ], + "integrationCode": "rs_russian_post", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "boxberry-old2": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Boxberry old2", + "code": "boxberry-old2", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "boxberry-1-5bbdc1cb90c32", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "glavpunkt-test": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Главпункт", + "code": "glavpunkt-test", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Тестовая доставка Главпункт", + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "e-money", + "cash" + ], + "integrationCode": "ts-delivery-integration-glavpunkt", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "20.00" + }, + "boxberry-symf4-local-test1-id6": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "boxberry-symf4-local-test1-id6", + "code": "boxberry-symf4-local-test1-id6", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "boxberry-symf4-local-test1-id6", + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "boxberry-6", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "boxberrys": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "BoxberryS", + "code": "boxberrys", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "BoxberryS", + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "boxberry-6", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1571123788": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1571123788", + "code": "dict-deliverytypes-1571123788", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1575878849": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1575878849", + "code": "dict-deliverytypes-1575878849", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "test2": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "test", + "code": "test2", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "e-money" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "none" + }, + "iml-1-integra": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "iml-1-integra", + "code": "iml-1-integra", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "iml-1-integra", + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "iml-7", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "peshkariki": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Пешкарики", + "code": "peshkariki", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "rs_peshkariki", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "guru": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Dostavka.GURU", + "code": "guru", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "ts-delivery-integration-dostavka-guru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "iml-2-integra": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "iml-2-integra", + "code": "iml-2-integra", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "iml-2-integra", + "paymentTypes": [ + "cash", + "e-money" + ], + "integrationCode": "iml-8", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1571123849": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1571123849", + "code": "dict-deliverytypes-1571123849", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1571124853": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1571124853", + "code": "dict-deliverytypes-1571124853", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1575878958": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1575878958", + "code": "dict-deliverytypes-1575878958", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "boxberrys-1": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "BoxberryS", + "code": "boxberrys-1", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "boxberry-17", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "iml": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "IML", + "code": "iml", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "iml-1", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1571134090": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1571134090", + "code": "dict-deliverytypes-1571134090", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1571134207": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1571134207", + "code": "dict-deliverytypes-1571134207", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1581413625": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1581413625", + "code": "dict-deliverytypes-1581413625", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1581413628": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1581413628", + "code": "dict-deliverytypes-1581413628", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-5e60fe6f05190": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-5e60fe6f05190", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1581413632": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1581413632", + "code": "dict-deliverytypes-1581413632", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ems": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "EMS Почта России", + "code": "ems", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "e-money", + "credit" + ], + "deliveryServices": [ + "2" + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1581413636": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1581413636", + "code": "dict-deliverytypes-1581413636", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "boxberry": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Boxberry-249", + "code": "boxberry", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "boxberry-249", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1581413758": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1581413758", + "code": "dict-deliverytypes-1581413758", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1581413764": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1581413764", + "code": "dict-deliverytypes-1581413764", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1581413767": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1581413767", + "code": "dict-deliverytypes-1581413767", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1581413776": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Bbbdict-deliverytypes-1581413776", + "code": "dict-deliverytypes-1581413776", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "easyway": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "easyway", + "code": "easyway", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "easyway", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "goods-order-packing": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Забор груза Goods", + "code": "goods-order-packing", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "goods", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "test": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Test", + "code": "test", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "18.00" + }, + "ozon-seller-5f2432a06a903": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-5f2432a06a903", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "7" + ], + "integrationCode": "ozon-seller-5f2432a06a903", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-5f2bc923ebb33": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-5f2bc923ebb33", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + + ], + "integrationCode": "ozon-seller-5f2bc923ebb33", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-5f689f4509b2f": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-5f689f4509b2f", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "423361", + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "442100" + ], + "integrationCode": "ozon-seller-5f689f4509b2f", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-228": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-228", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "e-money", + "555161", + "555159", + "776106", + "442099", + "442100", + "423361", + "cash", + "818241", + "credit", + "bank-transfer", + "897285", + "bank-card", + "bonuses-sl", + "7" + ], + "integrationCode": "ozon-seller-5f22c13b25e4d", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dw-courierist": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "dw_courierist", + "code": "dw-courierist", + "active": false, + "defaultCost": 200, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "courierist-2-5f771025370b2", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "none" + }, + "dw-courierist123": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "dw_courierist123", + "code": "dw-courierist123", + "active": false, + "defaultCost": 200, + "defaultNetCost": 200, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "courierist-4-5f77202ca9d60", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "none" + }, + "courierist1": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Курьерист доставка", + "code": "courierist1", + "active": false, + "defaultCost": 220, + "defaultNetCost": 220, + "paymentTypes": [ + + ], + "integrationCode": "courierist-6-5f772ad6d0b48", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "none" + }, + "dw-courierist1231212": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Курьерист доставка 111", + "code": "dw-courierist1231212", + "active": false, + "defaultCost": 11, + "defaultNetCost": 11, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "courierist-7-5f7738a177805", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "cour": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Курьерист", + "code": "cour", + "active": false, + "defaultCost": 22, + "defaultNetCost": 22, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "courierist-6-5f7ae74ae5fa4", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "none" + }, + "apiship": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "ApiShip", + "code": "apiship", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "cash" + ], + "integrationCode": "apiship", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dwboxberry": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "dw boxberry", + "code": "dwboxberry", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "cash", + "e-money", + "credit", + "bank-transfer", + "bank-card" + ], + "integrationCode": "boxberry-1-5f97ee0d246e3", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-5f9bfac6a9855": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-5f9bfac6a9855", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "442100", + "423361" + ], + "integrationCode": "ozon-seller-5f9bfac6a9855", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-5f8fe602d193b": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-5f8fe602d193b", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "442100", + "423361" + ], + "integrationCode": "ozon-seller-5f8fe602d193b", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "delivery-callback-check": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "delivery-callback-check", + "code": "delivery-callback-check", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "delivery-callback-check\r\n\"baseUrl\":\"https://webhook.site/87b71464-b6e8-4e39-8c98-9519fc82aa44\"\r\n\"actions\":{\"shipmentPointList\":\"?action=shipmentPointList\",\"calculate\":\"?action=calculate\",\"save\":\"?action=save\",\"delete\":\"?action=delete\",\"print\":\"?action=print\"}", + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money", + "7", + "bonuses-sl", + "897285", + "818241", + "423361", + "442100", + "442099", + "776106", + "555159", + "555161" + ], + "integrationCode": "delivery-callback-check", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON 5f4766d858f78", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "423361", + "442099", + "442100", + "555159", + "555161", + "776106", + "818241", + "897285" + ], + "integrationCode": "ozon-seller-5f4766d858f78", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-5f6affbcb5617": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-5f6affbcb5617", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "423361", + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "442100" + ], + "integrationCode": "ozon-seller-5f6affbcb5617", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dw-boxberry": { + "isDynamicCostCalculation": true, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "dw_boxberry", + "code": "dw-boxberry", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "boxberry-1-5f97ee0d246e3", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "self-delivery": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Самовывоз ндс-20", + "code": "self-delivery", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "e-money", + "credit", + "cash", + "alfa-bank-payment", + "checkbox-payment", + "checkbox-payment-two", + "invoice-payment" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "russian-post": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Почта России", + "code": "russian-post", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "e-money", + "credit", + "alfa-bank-payment", + "checkbox-payment", + "checkbox-payment-two", + "invoice-payment" + ], + "deliveryServices": [ + "3" + ], + "defaultForCrm": false + }, + "dhl": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "DHL", + "code": "dhl", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "dhl-5bbde05af3c11", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-2": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-2", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "423361", + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "442100", + "test-mc-payment", + "test-mc-cashin", + "test-mc-prepayment", + "12", + "13", + "18", + "14", + "19", + "15", + "16", + "17", + "20", + "test-payment-integration", + "invoice-payment", + "alfa-bank-payment", + "checkbox-payment", + "checkbox-payment-two", + "wallete-one-payment-vog-old" + ], + "integrationCode": "ozon-seller-2", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-3": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-3", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "442100", + "423361" + ], + "integrationCode": "ozon-seller-3", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-5f9d10257668e": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-5f9d10257668e", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "442100", + "423361" + ], + "integrationCode": "ozon-seller-5f9d10257668e", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "servientrega-test": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Servientrega test", + "code": "servientrega-test", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "e-money", + "cash", + "7", + "bonuses-sl", + "897285", + "818241", + "423361", + "442100", + "442099", + "776106", + "555159", + "555161" + ], + "integrationCode": "servientrega-1", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-1": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-1", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-2": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-2", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-3": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-3", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-4": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-4", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-5": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-5", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-6": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-6", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-7": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-7", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-8": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-8", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-9": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-9", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-10": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-10", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-11": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-11", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-12": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-12", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-14": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-14", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-13": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-13", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-15": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-15", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-16": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-16", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-17": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-17", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-18": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-18", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-19": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-19", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-20": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-20", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-21": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-21", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-22": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-22", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-23": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-23", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-28": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-28", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-24": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-24", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-25": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-25", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-29": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-29", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-32": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-32", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-26": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-26", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-27": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-27", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-30": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-30", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-31": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-31", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-33": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-33", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-34": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-34", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "serv-test": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "serv test", + "code": "serv-test", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "7", + "bonuses-sl", + "bank-card", + "897285", + "bank-transfer", + "credit", + "818241", + "cash", + "423361", + "442100", + "442099", + "776106", + "555159", + "555161", + "e-money" + ], + "integrationCode": "servientrega-1", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "boxberry-ma": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Boxberry Test MA", + "code": "boxberry-ma", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "boxberry-1-5fd86cedab879", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "boxberry-fake": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Boxberry Fake", + "code": "boxberry-fake", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "boxberry-2-5fe1ee565a534", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "boxberry-fake2": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Boxberry Fake", + "code": "boxberry-fake2", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "boxberry-4-5fe1f01c5469c", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "boxberry-ma-2": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Boxberry Test MA", + "code": "boxberry-ma-2", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "test-mc-payment", + "test-mc-prepayment", + "test-mc-cashin", + "e-money" + ], + "integrationCode": "boxberry-2-5fd8719995974", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "boxberry-delivery-10": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Boxberry Test MA", + "code": "boxberry-delivery-10", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "boxberry-10", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "boxt": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "boxt", + "code": "boxt", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "boxberry-8", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "none" + }, + "boxt2": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "boxt2", + "code": "boxt2", + "active": true, + "defaultCost": 50, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "boxberry-9", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "none" + }, + "boxberry-throwaway-12": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Boxberry Throwaway", + "code": "boxberry-throwaway-12", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "boxberry-12", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "dict-deliverytypes-1571124916": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Test Type", + "code": "dict-deliverytypes-1571124916", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit" + ], + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "boxberry-9": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "boxberry-9", + "code": "boxberry-9", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "boxberry-9", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "test-cour": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Тестовый курьерист", + "code": "test-cour", + "active": false, + "defaultCost": 22, + "defaultNetCost": 22, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "courierist-6-5f7ae74ae5fa4", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "20.00" + }, + "courierist-new-test": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "courierist-Pekar", + "code": "courierist-new-test", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "courierist-10", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "courier-internal": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Курьером (свои курьеры)", + "code": "courier-internal", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "cash" + ], + "integrationCode": "courier", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "test-ma": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Тестовая МА", + "code": "test-ma", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "cash", + "bank-transfer" + ], + "integrationCode": "test-integration_70156", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "20.00" + }, + "iml-double-156": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "IML-double-156", + "code": "iml-double-156", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "iml-156", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "20.00" + }, + "sdek": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "СДЭК", + "code": "sdek", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "sdek", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "defaultTariffCode": "139", + "defaultTariffType": "courier", + "defaultTariffName": "Посылка дверь-дверь" + }, + "yandex-beru-delivery-35": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-35", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-36": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-36", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-39": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-39", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-37": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-37", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-38": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-38", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-40": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-40", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-41": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-41", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-42": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-42", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-43": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-43", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-44": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-44", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-45": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-45", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-46": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-46", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-47": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-47", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-48": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-48", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-49": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-49", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-50": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-50", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-51": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-51", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-5ff57fbceb6b2": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-5ff57fbceb6b2", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "423361", + "442100", + "test-mc-payment", + "test-mc-cashin", + "test-mc-prepayment" + ], + "integrationCode": "ozon-seller-5ff57fbceb6b2", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-6023f9ca1d880": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-6023f9ca1d880", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "423361", + "442100", + "test-mc-payment", + "test-mc-cashin", + "test-mc-prepayment", + "12", + "13", + "18", + "14", + "19", + "15", + "16", + "17", + "20", + "checkbox-payment", + "checkbox-payment-two" + ], + "integrationCode": "ozon-seller-6023f9ca1d880", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-5f9bff5857b35": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-5f9bff5857b35", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "442100", + "423361" + ], + "integrationCode": "ozon-seller-5f9bff5857b35", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-5fda1144d3c1e": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-5fda1144d3c1e", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "423361", + "442100", + "test-mc-payment", + "test-mc-cashin", + "test-mc-prepayment" + ], + "integrationCode": "ozon-seller-5fda1144d3c1e", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-6023fbcea632c": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-6023fbcea632c", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "423361", + "442100", + "test-mc-payment", + "test-mc-cashin", + "test-mc-prepayment", + "12", + "13", + "18", + "14", + "19", + "15", + "16", + "17", + "20", + "checkbox-payment", + "checkbox-payment-two" + ], + "integrationCode": "ozon-seller-6023fbcea632c", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "ozon-seller-6024d760b14f8": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка OZON", + "code": "ozon-seller-6024d760b14f8", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "description": "Доставка OZON", + "paymentTypes": [ + "cash", + "bank-card", + "e-money", + "bank-transfer", + "credit", + "bonuses-sl", + "dict-paymenttypes-1571123795", + "dict-paymenttypes-1575878858", + "dict-paymenttypes-1571123853", + "dict-paymenttypes-1571124860", + "dict-paymenttypes-1575878963", + "dict-paymenttypes-1571124919", + "dict-paymenttypes-1571134100", + "dict-paymenttypes-1571134212", + "7", + "dict-paymenttypes-1581413638", + "dict-paymenttypes-1581413641", + "dict-paymenttypes-1581413645", + "dict-paymenttypes-1581413649", + "dict-paymenttypes-1581413768", + "dict-paymenttypes-1581413773", + "dict-paymenttypes-1581413777", + "dict-paymenttypes-1581413785", + "442099", + "555159", + "555161", + "776106", + "818241", + "897285", + "423361", + "442100", + "test-mc-payment", + "test-mc-cashin", + "test-mc-prepayment", + "12", + "13", + "18", + "14", + "19", + "15", + "16", + "17", + "20", + "checkbox-payment", + "checkbox-payment-two", + "test-payment-integration", + "invoice-payment", + "alfa-bank-payment", + "wallete-one-payment-vog-old" + ], + "integrationCode": "ozon-seller-6024d760b14f8", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-52": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-52", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-53": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-53", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-54": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-54", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-55": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-55", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-56": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-56", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-57": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-57", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "cdek-dev-2": { + "isDynamicCostCalculation": true, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "CDEK dev", + "code": "cdek-dev-2", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "cdek_dev_4", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "nova-poshta-standart": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Новая Почта Стандартная", + "code": "nova-poshta-standart", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ], + "integrationCode": "newpost", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "20.00" + }, + "cdek-dev": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "CDEK dev", + "code": "cdek-dev", + "active": false, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "cdek_dev", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "10.00" + }, + "cdek-dev-1": { + "isDynamicCostCalculation": true, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "CDEK dev", + "code": "cdek-dev-1", + "active": false, + "defaultCost": 100, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "cash", + "442100" + ], + "integrationCode": "cdek_dev_2", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "yandex-beru-delivery-58": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка Беру", + "code": "yandex-beru-delivery-58", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "yandex_beru", + "deliveryServices": [ + + ], + "defaultForCrm": false + }, + "courier": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": false, + "isAutoNetCostCalculation": false, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Доставка курьером", + "code": "courier", + "active": true, + "defaultCost": 300, + "defaultNetCost": 0, + "paymentTypes": [ + "cash", + "bank-card", + "bank-transfer", + "e-money", + "credit", + "test-payment-integration" + ], + "deliveryServices": [ + "1", + "pvz-out2", + "pvz-out3" + ], + "defaultForCrm": false, + "vatRate": "10.50" + }, + "cdek-dev-3": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "CDEK dev", + "code": "cdek-dev-3", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + "bank-card", + "cash", + "442100" + ], + "integrationCode": "cdek_dev_2", + "deliveryServices": [ + + ], + "defaultForCrm": false, + "vatRate": "20.00" + }, + "crrst-24": { + "isDynamicCostCalculation": false, + "isAutoCostCalculation": true, + "isAutoNetCostCalculation": true, + "isCostDependsOnRegionAndWeightAndSum": false, + "isCostDependsOnDateTime": false, + "name": "Курьерист 24", + "code": "crrst-24", + "active": true, + "defaultCost": 0, + "defaultNetCost": 0, + "paymentTypes": [ + + ], + "integrationCode": "courierist-24", + "deliveryServices": [ + + ], + "defaultForCrm": false + } + } +} +EOF; + + $mock = static::createApiMockBuilder('reference/delivery-types'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->deliveryTypes(); + + self::assertModelsCallback($json, $response, static function ($expected, $actual) { + foreach ($expected['deliveryTypes'] as $key => $deliveryType) { + $actual['deliveryTypes'][$key]['isDynamicCostCalculation'] = $deliveryType['isDynamicCostCalculation']; + $actual['deliveryTypes'][$key]['isAutoCostCalculation'] = $deliveryType['isAutoCostCalculation']; + $actual['deliveryTypes'][$key]['isAutoNetCostCalculation'] = $deliveryType['isAutoNetCostCalculation']; + $actual['deliveryTypes'][$key]['isCostDependsOnRegionAndWeightAndSum'] + = $deliveryType['isCostDependsOnRegionAndWeightAndSum']; + $actual['deliveryTypes'][$key]['isCostDependsOnDateTime'] = $deliveryType['isCostDependsOnDateTime']; + } + + self::assertEquals($expected, $actual); + }); + } + + public function testDeliveryTypesEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new DeliveryType(); + $entity->name = 'Test Type'; + $entity->active = false; + $entity->defaultCost = 0; + $entity->defaultNetCost = 0; + $entity->paymentTypes = [ + 'bank-card', + 'bank-transfer', + 'credit', + 'cash', + 'e-money' + ]; + + $request = new DeliveryTypesEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/delivery-types/dict-deliverytypes-1571124916/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->deliveryTypesEdit('dict-deliverytypes-1571124916', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testLegalEntities(): void + { + $json = <<<'EOF' +{ + "success": true, + "legalEntities": [ + { + "contragentType": "enterpreneur", + "legalName": "NDS", + "code": "NDS", + "countryIso": "RU", + "vatRate": "20.00" + }, + { + "contragentType": "enterpreneur", + "legalName": "ИП Бороздина Татьяна Александровна", + "INN": "773719480154", + "code": "Vog_Gallery", + "countryIso": "RU" + }, + { + "contragentType": "legal-entity", + "legalName": "ООО \"Казематы\"", + "code": "kazemat", + "countryIso": "RU", + "vatRate": "18.00" + }, + { + "contragentType": "legal-entity", + "legalName": "ООО \"Некрополь\"", + "code": "necropol", + "countryIso": "RU", + "vatRate": "20.00" + } + ] +} +EOF; + + $mock = static::createApiMockBuilder('reference/legal-entities'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->legalEntities(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testLegalEntitiesEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new LegalEntity(); + $entity->contragentType = "legal-entity"; + $entity->legalName = "ООО \"Некрополь\""; + $entity->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; + $entity->vatRate = "20.00"; + + $request = new LegalEntityEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/legal-entities/necropol/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->legalEntitiesEdit('necropol', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testMgChannels(): void + { + $json = <<<'EOF' +{ + "success": true, + "mgChannels": [ + { + "id": 6, + "externalId": 3, + "type": "viber", + "active": false, + "name": "AzgalotNeBot" + }, + { + "id": 7, + "externalId": 6, + "type": "fbmessenger", + "active": false + }, + { + "id": 8, + "externalId": 7, + "type": "viber", + "active": false, + "name": "transportest" + }, + { + "id": 9, + "externalId": 9, + "type": "whatsapp", + "active": false, + "name": "transports" + } + ] +} +EOF; + + $mock = static::createApiMockBuilder('reference/mg-channels'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->mgChannels(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testOrderMethods(): void + { + $json = <<<'EOF' +{ + "success": true, + "orderMethods": { + "2": { + "name": "2", + "code": "2", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "isFromPos": false + }, + "phone": { + "name": "По телефону", + "code": "phone", + "active": true, + "defaultForCrm": true, + "defaultForApi": false, + "isFromPos": false + }, + "shopping-cart": { + "name": "Через корзину", + "code": "shopping-cart", + "active": true, + "defaultForCrm": false, + "defaultForApi": true, + "isFromPos": false + }, + "one-click": { + "name": "В один клик", + "code": "one-click", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "isFromPos": false + }, + "price-decrease-request": { + "name": "Запрос на понижение цены", + "code": "price-decrease-request", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "isFromPos": false + }, + "landing-page": { + "name": "Заявка с посадочной страницы", + "code": "landing-page", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "isFromPos": false + }, + "offline": { + "name": "Оффлайн", + "code": "offline", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "isFromPos": false + }, + "app": { + "name": "Мобильное приложение", + "code": "app", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "isFromPos": false + }, + "live-chat": { + "name": "Онлайн-консультант", + "code": "live-chat", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "isFromPos": false + }, + "terminal": { + "name": "Терминал", + "code": "terminal", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "isFromPos": false + }, + "missed-call": { + "name": "Пропущенный звонок", + "code": "missed-call", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "isFromPos": false + }, + "messenger": { + "name": "Мессенджеры", + "code": "messenger", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "isFromPos": false + } + } +} +EOF; + + $mock = static::createApiMockBuilder('reference/order-methods'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->orderMethods(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testOrderMethodsEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new OrderMethod(); + $entity->name = 'Test Method'; + $entity->active = true; + + $request = new OrderMethodsEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/order-methods/2/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->orderMethodsEdit('2', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testOrderTypes(): void + { + $json = <<<'EOF' +{ + "success": true, + "orderTypes": { + "1": { + "name": "1", + "code": "1", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "eshop-individual": { + "name": "Физическое лицо", + "code": "eshop-individual", + "active": true, + "defaultForCrm": true, + "defaultForApi": false, + "ordering": 990 + }, + "test": { + "name": "test", + "code": "test", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "eshop-legal": { + "name": "Юридическое лицо", + "code": "eshop-legal", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1571123791": { + "name": "Bbbdict-ordertypes-1571123791", + "code": "dict-ordertypes-1571123791", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1575878853": { + "name": "Bbbdict-ordertypes-1575878853", + "code": "dict-ordertypes-1575878853", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1571123851": { + "name": "Bbbdict-ordertypes-1571123851", + "code": "dict-ordertypes-1571123851", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1571124857": { + "name": "Bbbdict-ordertypes-1571124857", + "code": "dict-ordertypes-1571124857", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1575878961": { + "name": "Bbbdict-ordertypes-1575878961", + "code": "dict-ordertypes-1575878961", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1571124917": { + "name": "Bbbdict-ordertypes-1571124917", + "code": "dict-ordertypes-1571124917", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1571134095": { + "name": "Bbbdict-ordertypes-1571134095", + "code": "dict-ordertypes-1571134095", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1571134209": { + "name": "Bbbdict-ordertypes-1571134209", + "code": "dict-ordertypes-1571134209", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1581413631": { + "name": "Bbbdict-ordertypes-1581413631", + "code": "dict-ordertypes-1581413631", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1581413635": { + "name": "Bbbdict-ordertypes-1581413635", + "code": "dict-ordertypes-1581413635", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1581413638": { + "name": "Bbbdict-ordertypes-1581413638", + "code": "dict-ordertypes-1581413638", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1581413643": { + "name": "Bbbdict-ordertypes-1581413643", + "code": "dict-ordertypes-1581413643", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1581413763": { + "name": "Bbbdict-ordertypes-1581413763", + "code": "dict-ordertypes-1581413763", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1581413769": { + "name": "Bbbdict-ordertypes-1581413769", + "code": "dict-ordertypes-1581413769", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1581413772": { + "name": "Bbbdict-ordertypes-1581413772", + "code": "dict-ordertypes-1581413772", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "dict-ordertypes-1581413780": { + "name": "Bbbdict-ordertypes-1581413780", + "code": "dict-ordertypes-1581413780", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + }, + "mc-order": { + "name": "Заказ для МС", + "code": "mc-order", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "ordering": 990 + } + } +} +EOF; + + $mock = static::createApiMockBuilder('reference/order-types'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->orderTypes(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testOrderTypesEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new OrderType(); + $entity->name = 'Test Type'; + $entity->active = true; + + $request = new OrderTypesEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/order-types/1/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->orderTypesEdit('1', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testPaymentStatuses(): void + { + $json = <<<'EOF' +{ + "success": true, + "paymentStatuses": { + "dict-paymentstatuses-1571123793": { + "name": "Bbbdict-paymentstatuses-1571123793", + "code": "dict-paymentstatuses-1571123793", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1571123852": { + "name": "Bbbdict-paymentstatuses-1571123852", + "code": "dict-paymentstatuses-1571123852", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1571124858": { + "name": "Bbbdict-paymentstatuses-1571124858", + "code": "dict-paymentstatuses-1571124858", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1571124918": { + "name": "Bbbdict-paymentstatuses-1571124918", + "code": "dict-paymentstatuses-1571124918", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "payment-start": { + "name": "Платеж проведен", + "code": "payment-start", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 30, + "paymentTypes": [ + "bonuses-sl", + "test-payment-integration", + "alfa-bank-payment", + "bank-card", + "bank-transfer", + "credit", + "cash", + "invoice-payment", + "wallete-one-payment-vog", + "test-mc-payment", + "test-mc-cashin", + "e-money" + ] + }, + "paid": { + "name": "Оплачен", + "code": "paid", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": true, + "ordering": 60, + "paymentTypes": [ + "bonuses-sl", + "17", + "test-payment-integration", + "alfa-bank-payment", + "bank-card", + "897285", + "bank-transfer", + "12", + "credit", + "818241", + "cash", + "13", + "423361", + "442100", + "442099", + "14", + "19", + "18", + "checkbox-payment", + "checkbox-payment-two", + "invoice-payment", + "wallete-one-payment-vog", + "776106", + "15", + "20", + "16", + "test-mc-payment", + "test-mc-prepayment", + "test-mc-cashin", + "555159", + "555161", + "e-money" + ] + }, + "credit-check": { + "name": "Проверка документов на кредит", + "code": "credit-check", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 40, + "paymentTypes": [ + "bonuses-sl", + "credit", + "cash" + ] + }, + "credit-approved": { + "name": "Кредит одобрен", + "code": "credit-approved", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 50, + "paymentTypes": [ + "bonuses-sl", + "credit", + "cash" + ] + }, + "fail": { + "name": "Ошибка", + "code": "fail", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 55, + "paymentTypes": [ + "bonuses-sl", + "bank-card", + "bank-transfer", + "credit", + "cash", + "e-money" + ] + }, + "not-paid": { + "name": "Не оплачен", + "code": "not-paid", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 10, + "paymentTypes": [ + "bonuses-sl", + "17", + "bank-card", + "bank-transfer", + "12", + "credit", + "cash", + "13", + "14", + "19", + "18", + "checkbox-payment", + "15", + "20", + "16", + "test-mc-payment", + "test-mc-cashin", + "e-money" + ] + }, + "dict-paymentstatuses-1571134098": { + "name": "Bbbdict-paymentstatuses-1571134098", + "code": "dict-paymentstatuses-1571134098", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1571134211": { + "name": "Bbbdict-paymentstatuses-1571134211", + "code": "dict-paymentstatuses-1571134211", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1575878855": { + "name": "Bbbdict-paymentstatuses-1575878855", + "code": "dict-paymentstatuses-1575878855", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1575878962": { + "name": "Bbbdict-paymentstatuses-1575878962", + "code": "dict-paymentstatuses-1575878962", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1581413635": { + "name": "Bbbdict-paymentstatuses-1581413635", + "code": "dict-paymentstatuses-1581413635", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1581413638": { + "name": "Bbbdict-paymentstatuses-1581413638", + "code": "dict-paymentstatuses-1581413638", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1581413641": { + "name": "Bbbdict-paymentstatuses-1581413641", + "code": "dict-paymentstatuses-1581413641", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1581413646": { + "name": "Bbbdict-paymentstatuses-1581413646", + "code": "dict-paymentstatuses-1581413646", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1581413765": { + "name": "Bbbdict-paymentstatuses-1581413765", + "code": "dict-paymentstatuses-1581413765", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1581413771": { + "name": "Bbbdict-paymentstatuses-1581413771", + "code": "dict-paymentstatuses-1581413771", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1581413775": { + "name": "Bbbdict-paymentstatuses-1581413775", + "code": "dict-paymentstatuses-1581413775", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "dict-paymentstatuses-1581413783": { + "name": "Bbbdict-paymentstatuses-1581413783", + "code": "dict-paymentstatuses-1581413783", + "active": false, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "cash" + ] + }, + "pending": { + "name": "Не оплачен", + "code": "pending", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "897285", + "bank-transfer", + "818241", + "cash", + "423361", + "442100", + "442099", + "checkbox-payment-two", + "776106", + "555159", + "555161" + ] + }, + "invoice": { + "name": "Выставлен счет", + "code": "invoice", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 20, + "paymentTypes": [ + "bonuses-sl", + "test-payment-integration", + "alfa-bank-payment", + "bank-card", + "bank-transfer", + "credit", + "cash", + "invoice-payment", + "wallete-one-payment-vog", + "test-mc-payment", + "test-mc-cashin", + "e-money" + ] + }, + "fiskalizirovan": { + "name": "Фискализирован", + "code": "fiskalizirovan", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": true, + "ordering": 990, + "paymentTypes": [ + "checkbox-payment", + "checkbox-payment-two" + ] + }, + "check-refund": { + "name": "Возврат чека", + "code": "check-refund", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "test-payment-integration", + "alfa-bank-payment", + "checkbox-payment", + "checkbox-payment-two", + "invoice-payment", + "wallete-one-payment-vog" + ] + }, + "check-refund-after": { + "name": "Чек возвращён", + "code": "check-refund-after", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "paymentComplete": false, + "ordering": 990, + "paymentTypes": [ + "test-payment-integration", + "alfa-bank-payment", + "checkbox-payment", + "checkbox-payment-two", + "invoice-payment", + "wallete-one-payment-vog" + ] + } + } +} +EOF; + + $mock = static::createApiMockBuilder('reference/payment-statuses'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->paymentStatuses(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testPaymentStatusesEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new PaymentStatus(); + $entity->name = 'Test Status'; + $entity->active = true; + $entity->ordering = 990; + $entity->paymentTypes = ['cash']; + + $request = new PaymentStatusesEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/payment-statuses/dict-paymentstatuses-1571123793/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->paymentStatusesEdit('dict-paymentstatuses-1571123793', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testPaymentTypes(): void + { + $json = <<<'EOF' +{ + "success": true, + "paymentTypes": { + "7": { + "name": "7", + "code": "7", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "deliveryTypes": [ + "2", + "ozon-seller-5f2432a06a903", + "ozon-seller", + "ozon-seller-5f689f4509b2f", + "ozon-seller-228", + "ozon-seller-2", + "ozon-seller-5f6affbcb5617", + "servientrega-test", + "ozon-seller-5f8fe602d193b", + "ozon-seller-5f9bfac6a9855", + "ozon-seller-5f9bff5857b35", + "delivery-callback-check", + "ozon-seller-5f9d10257668e", + "ozon-seller-3", + "serv-test", + "ozon-seller-5fda1144d3c1e", + "ozon-seller-5ff57fbceb6b2", + "ozon-seller-6023f9ca1d880", + "ozon-seller-6023fbcea632c", + "ozon-seller-6024d760b14f8" + ], + "paymentStatuses": [ + + ] + }, + "12": { + "name": "Безналичный расчет", + "code": "12", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "description": "", + "deliveryTypes": [ + "3", + "ozon-seller-6023f9ca1d880", + "ozon-seller-6023fbcea632c", + "ozon-seller-6024d760b14f8" + ], + "paymentStatuses": [ + "not-paid", + "paid" + ] + }, + "13": { + "name": "Наличными", + "code": "13", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "description": "", + "deliveryTypes": [ + "11", + "8", + "9", + "3", + "ozon-seller-6023f9ca1d880", + "ozon-seller-6023fbcea632c", + "ozon-seller-6024d760b14f8" + ], + "paymentStatuses": [ + "not-paid", + "paid" + ] + }, + "14": { + "name": "Онлайн-оплата банковской картой", + "code": "14", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "description": "", + "deliveryTypes": [ + "8", + "9", + "3", + "ozon-seller-6023f9ca1d880", + "ozon-seller-6023fbcea632c", + "ozon-seller-6024d760b14f8" + ], + "paymentStatuses": [ + "not-paid", + "paid" + ] + }, + "15": { + "name": "Оплата при получении", + "code": "15", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "description": "", + "deliveryTypes": [ + "11", + "3", + "ozon-seller-6023f9ca1d880", + "ozon-seller-6023fbcea632c", + "ozon-seller-6024d760b14f8" + ], + "paymentStatuses": [ + "not-paid", + "paid" + ] + }, + "wallete-one-payment-vog": { + "name": "Оплата Wallet One (Vog)", + "code": "wallete-one-payment-vog", + "active": true, + "defaultForCrm": false, + "defaultForApi": false, + "deliveryTypes": [ + + ], + "paymentStatuses": [ + "invoice", + "payment-start", + "paid", + "check-refund", + "check-refund-after" + ], + "integrationModule": { + "active": true, + "name": "Wallet One", + "logo": "http://argo.inet-cool.ru/img/w1.svg" + } + } + } +} +EOF; + + $mock = static::createApiMockBuilder('reference/payment-types'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->paymentTypes(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testPaymentTypesEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new PaymentType(); + $entity->name = "Test Integration Payment"; + $entity->code = "test-payment-integration"; + $entity->active = true; + $entity->defaultForCrm = false; + $entity->defaultForApi = false; + $entity->paymentStatuses = [ + "invoice", + "payment-start", + "paid", + "check-refund", + "check-refund-after" + ]; + + $request = new PaymentTypesEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/payment-types/test-payment-integration/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->paymentTypesEdit('test-payment-integration', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testPriceTypes(): void + { + $json = <<<'EOF' +{ + "success": true, + "priceTypes": [ + { + "id": 7, + "code": "sample_v4_price_code", + "name": "Sample v4 price type", + "active": true, + "default": false, + "geo": [ + + ], + "groups": [ + + ], + "ordering": 500 + }, + { + "id": 5, + "code": "name", + "name": "Название", + "active": true, + "default": false, + "geo": [ + + ], + "groups": [ + + ], + "ordering": 990 + }, + { + "id": 6, + "code": "test2", + "name": "test2", + "active": true, + "default": false, + "geo": [ + + ], + "groups": [ + + ], + "ordering": 990 + }, + { + "id": 4, + "code": "base", + "name": "Базовая", + "active": true, + "default": true, + "geo": [ + + ], + "groups": [ + + ], + "ordering": 991 + } + ] +} +EOF; + + $mock = static::createApiMockBuilder('reference/price-types'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->priceTypes(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testPriceTypesEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new PriceType(); + $entity->name = "Test Price Type"; + $entity->active = true; + $entity->ordering = 980; + + $request = new PriceTypesEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/price-types/test2/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->priceTypesEdit('test2', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testProductStatuses(): void + { + $json = <<<'EOF' +{ + "success": true, + "productStatuses": { + "1": { + "code": "1", + "ordering": 140, + "active": true, + "createdAt": "2019-02-07 16:55:29", + "cancelStatus": false, + "name": "1" + }, + "4": { + "code": "4", + "ordering": 270, + "active": false, + "createdAt": "2019-10-01 11:24:08", + "cancelStatus": false, + "name": "4" + }, + "otmena": { + "code": "otmena", + "ordering": 360, + "active": true, + "createdAt": "2020-01-14 11:34:24", + "cancelStatus": true, + "name": "otmena" + }, + "new": { + "code": "new", + "ordering": 10, + "active": true, + "createdAt": "2018-09-04 16:35:03", + "cancelStatus": false, + "name": "Добавлен" + }, + "confirming": { + "code": "confirming", + "ordering": 20, + "active": true, + "createdAt": "2018-09-04 16:35:03", + "cancelStatus": false, + "name": "Подтверждение наличия" + }, + "in-reserve": { + "code": "in-reserve", + "ordering": 30, + "active": true, + "createdAt": "2018-09-04 16:35:03", + "cancelStatus": false, + "name": "В резерве" + }, + "assembly-delivery": { + "code": "assembly-delivery", + "ordering": 40, + "active": true, + "createdAt": "2018-09-04 16:35:03", + "cancelStatus": false, + "name": "Доставляется на точку сборки" + }, + "ready-for-assembly": { + "code": "ready-for-assembly", + "ordering": 50, + "active": true, + "createdAt": "2018-09-04 16:35:03", + "cancelStatus": false, + "name": "Готов к комплектации" + }, + "completed": { + "code": "completed", + "ordering": 60, + "active": true, + "createdAt": "2018-09-04 16:35:03", + "cancelStatus": false, + "name": "Укомплектован" + }, + "saled": { + "code": "saled", + "ordering": 70, + "active": true, + "createdAt": "2018-09-04 16:35:03", + "orderStatusByProductStatus": "complete", + "orderStatusForProductStatus": "complete", + "cancelStatus": false, + "name": "Продан" + }, + "out-of-stock": { + "code": "out-of-stock", + "ordering": 80, + "active": true, + "createdAt": "2018-09-04 16:35:03", + "orderStatusByProductStatus": "no-product", + "orderStatusForProductStatus": "no-product", + "cancelStatus": true, + "name": "Нет в наличии" + }, + "failure": { + "code": "failure", + "ordering": 90, + "active": true, + "createdAt": "2018-09-04 16:35:03", + "cancelStatus": true, + "name": "Отказ клиента" + }, + "product-is-damaged": { + "code": "product-is-damaged", + "ordering": 100, + "active": true, + "createdAt": "2018-09-04 16:35:03", + "cancelStatus": true, + "name": "Товар поврежден" + }, + "assembly-not-delivery": { + "code": "assembly-not-delivery", + "ordering": 110, + "active": true, + "createdAt": "2018-09-04 16:35:03", + "cancelStatus": true, + "name": "Не доставлен на точку сборки" + }, + "lost": { + "code": "lost", + "ordering": 120, + "active": true, + "createdAt": "2018-09-04 16:35:03", + "cancelStatus": true, + "name": "Потерян" + }, + "returned": { + "code": "returned", + "ordering": 130, + "active": true, + "createdAt": "2018-09-04 16:35:03", + "cancelStatus": true, + "name": "Возврат" + }, + "1-1": { + "code": "1-1", + "ordering": 150, + "active": true, + "createdAt": "2019-02-07 16:55:38", + "cancelStatus": false, + "name": "1" + }, + "dsad-dsad-sdsad-ds-ds-ad-d": { + "code": "dsad-dsad-sdsad-ds-ds-ad-d", + "ordering": 160, + "active": true, + "createdAt": "2019-02-07 16:56:10", + "orderStatusByProductStatus": "test", + "orderStatusForProductStatus": "test", + "cancelStatus": false, + "name": "dsad\"Ds'aD\"Sdsad-Ds___ds-ad-=d" + }, + "": { + "code": "", + "ordering": 170, + "active": true, + "createdAt": "2019-02-07 16:56:10", + "cancelStatus": false, + "name": "+/-+/*++-+-/*+-+/*" + }, + "-1": { + "code": "-1", + "ordering": 180, + "active": true, + "createdAt": "2019-02-07 16:56:10", + "cancelStatus": false, + "name": "{}\"{>:<>?1" + }, + "-2": { + "code": "-2", + "ordering": 190, + "active": true, + "createdAt": "2019-02-07 16:57:56", + "cancelStatus": false, + "name": "+/-+/*++-+-/*+-+/*+/-+/*++-+-/*+-+/*" + }, + "-3": { + "code": "-3", + "ordering": 200, + "active": true, + "createdAt": "2019-02-07 16:58:09", + "cancelStatus": false, + "name": "+/-+/*++-+-/*+-+/*" + }, + "-4": { + "code": "-4", + "ordering": 210, + "active": true, + "createdAt": "2019-02-07 16:58:20", + "cancelStatus": false, + "name": "+/-+/*++-+-/*+-+/*" + }, + "fdsfsdfsdfsd": { + "code": "fdsfsdfsdfsd", + "ordering": 220, + "active": true, + "createdAt": "2019-02-07 16:58:20", + "cancelStatus": false, + "name": " " + }, + "-5": { + "code": "-5", + "ordering": 230, + "active": true, + "createdAt": "2019-02-07 16:58:20", + "cancelStatus": false, + "name": "{}\"{>:<>?" + }, + "nbsp": { + "code": "nbsp", + "ordering": 240, + "active": false, + "createdAt": "2019-02-07 17:16:55", + "cancelStatus": false, + "name": " " + }, + "yvayva": { + "code": "yvayva", + "ordering": 250, + "active": false, + "createdAt": "2019-02-07 17:16:55", + "cancelStatus": false, + "name": "ываыва" + }, + "-6": { + "code": "-6", + "ordering": 260, + "active": false, + "createdAt": "2019-02-21 10:50:17", + "cancelStatus": false, + "name": "+/-+/*++-+-/*+-+/*" + }, + "dict-productstatuses-1571134214": { + "code": "dict-productstatuses-1571134214", + "ordering": 280, + "active": false, + "createdAt": "2019-10-15 13:10:15", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1571134214" + }, + "dict-productstatuses-1571134105": { + "code": "dict-productstatuses-1571134105", + "ordering": 290, + "active": false, + "createdAt": "2019-10-15 13:08:25", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1571134105" + }, + "dict-productstatuses-1571124920": { + "code": "dict-productstatuses-1571124920", + "ordering": 300, + "active": false, + "createdAt": "2019-10-15 10:35:20", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1571124920" + }, + "dict-productstatuses-1571124862": { + "code": "dict-productstatuses-1571124862", + "ordering": 310, + "active": false, + "createdAt": "2019-10-15 10:34:22", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1571124862" + }, + "dict-productstatuses-1571123853": { + "code": "dict-productstatuses-1571123853", + "ordering": 320, + "active": false, + "createdAt": "2019-10-15 10:17:34", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1571123853" + }, + "dict-productstatuses-1571123796": { + "code": "dict-productstatuses-1571123796", + "ordering": 330, + "active": false, + "createdAt": "2019-10-15 10:16:36", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1571123796" + }, + "dict-productstatuses-1575878965": { + "code": "dict-productstatuses-1575878965", + "ordering": 340, + "active": false, + "createdAt": "2019-12-09 11:09:25", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1575878965" + }, + "dict-productstatuses-1575878860": { + "code": "dict-productstatuses-1575878860", + "ordering": 350, + "active": false, + "createdAt": "2019-12-09 11:07:40", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1575878860" + }, + "dict-productstatuses-1581413641": { + "code": "dict-productstatuses-1581413641", + "ordering": 990, + "active": false, + "createdAt": "2020-02-11 12:34:02", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1581413641" + }, + "dict-productstatuses-1581413644": { + "code": "dict-productstatuses-1581413644", + "ordering": 990, + "active": false, + "createdAt": "2020-02-11 12:34:05", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1581413644" + }, + "dict-productstatuses-1581413648": { + "code": "dict-productstatuses-1581413648", + "ordering": 990, + "active": false, + "createdAt": "2020-02-11 12:34:09", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1581413648" + }, + "dict-productstatuses-1581413652": { + "code": "dict-productstatuses-1581413652", + "ordering": 990, + "active": false, + "createdAt": "2020-02-11 12:34:14", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1581413652" + }, + "dict-productstatuses-1581413770": { + "code": "dict-productstatuses-1581413770", + "ordering": 990, + "active": false, + "createdAt": "2020-02-11 12:36:11", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1581413770" + }, + "dict-productstatuses-1581413776": { + "code": "dict-productstatuses-1581413776", + "ordering": 990, + "active": false, + "createdAt": "2020-02-11 12:36:17", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1581413776" + }, + "dict-productstatuses-1581413780": { + "code": "dict-productstatuses-1581413780", + "ordering": 990, + "active": false, + "createdAt": "2020-02-11 12:36:21", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1581413780" + }, + "dict-productstatuses-1581413788": { + "code": "dict-productstatuses-1581413788", + "ordering": 990, + "active": false, + "createdAt": "2020-02-11 12:36:29", + "cancelStatus": false, + "name": "Bbbdict-productstatuses-1581413788" + } + } +} +EOF; + + $mock = static::createApiMockBuilder('reference/product-statuses'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->productStatuses(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testProductStatusesEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new OrderProductStatus(); + $entity->name = "Test Product Status"; + $entity->active = true; + $entity->ordering = 980; + + $request = new ProductStatusesEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/product-statuses/nbsp/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->productStatusesEdit('nbsp', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testSites(): void + { + $json = <<<'EOF' +{ + "success": true, + "sites": { + "test": { + "name": "test", + "url": "https://example.com", + "code": "test", + "defaultForCrm": false, + "ymlUrl": "https://example.com", + "loadFromYml": true, + "catalogUpdatedAt": "2021-02-24 12:30:46", + "catalogLoadingAt": "2021-03-02 15:09:24", + "contragent": { + "contragentType": "enterpreneur", + "legalName": "ИП Бороздина Татьяна Александровна", + "INN": "773719480254", + "code": "test", + "countryIso": "RU" + }, + "countryIso": "RU", + "senderEmail": "test@example.com", + "senderName": "test", + "catalogId": "catalog", + "isCatalogMainSite": true + } + } +} +EOF; + + $mock = static::createApiMockBuilder('reference/sites'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->sites(); + + self::assertModelsCallback($json, $response, static function (array $expected, array $actual) { + foreach ($actual['sites'] as $key => $site) { + if ($site['contragent'] === $expected['sites'][$key]['contragent']['code']) { + $actual['sites'][$key]['contragent'] = $expected['sites'][$key]['contragent']; + } + } + + self::assertEquals($expected, $actual); + }); + } + + public function testSitesEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new Site(); + $entity->name = "Test Shop"; + $entity->url = "https://example.com"; + $entity->defaultForCrm = false; + $entity->ymlUrl = "https://example.com/test_catalog.xml"; + $entity->loadFromYml = true; + $entity->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION; + $entity->contragentCode = 'test-contragent'; + + $request = new SitesEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/sites/test-site/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->sitesEdit('test-site', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testStatusGroups(): void + { + $json = <<<'EOF' +{ + "success": true, + "statusGroups": { + "new": { + "name": "Новый", + "code": "new", + "active": true, + "ordering": 10, + "process": false, + "statuses": [ + "new", + "dict-statuses-1571123798", + "dict-statuses-1571123854", + "dict-statuses-1571124864", + "dict-statuses-1571124921", + "dict-statuses-1571134107", + "dict-statuses-1571134216", + "dict-statuses-1575878862", + "dict-statuses-1575878966", + "dict-statuses-1581413644", + "dict-statuses-1581413648", + "dict-statuses-1581413651", + "dict-statuses-1581413656", + "dict-statuses-1581413772", + "dict-statuses-1581413778", + "dict-statuses-1581413782", + "dict-statuses-1581413790" + ] + }, + "approval": { + "name": "Согласование", + "code": "approval", + "active": true, + "ordering": 20, + "process": true, + "statuses": [ + "availability-confirmed", + "offer-analog", + "client-confirmed", + "prepayed" + ] + }, + "assembling": { + "name": "Комплектация", + "code": "assembling", + "active": true, + "ordering": 30, + "process": true, + "statuses": [ + "send-to-assembling", + "assembling", + "assembling-complete", + "test" + ] + }, + "delivery": { + "name": "Доставка", + "code": "delivery", + "active": true, + "ordering": 40, + "process": true, + "statuses": [ + "send-to-delivery", + "delivering", + "redirect", + "delivered" + ] + }, + "complete": { + "name": "Выполнен", + "code": "complete", + "active": true, + "ordering": 50, + "process": false, + "statuses": [ + "complete", + "3" + ] + }, + "cancel": { + "name": "Отменен", + "code": "cancel", + "active": true, + "ordering": 60, + "process": false, + "statuses": [ + "no-call", + "no-product", + "already-buyed", + "delyvery-did-not-suit", + "prices-did-not-suit", + "cancel-other", + "testdubl", + "abandoned-cart" + ] + } + } +} +EOF; + + $mock = static::createApiMockBuilder('reference/status-groups'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->statusGroups(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testStatuses(): void + { + $json = <<<'EOF' +{ + "success": true, + "statuses": { + "3": { + "name": "3", + "code": "3", + "active": true, + "ordering": 990, + "group": "complete" + }, + "new": { + "name": "Новый", + "code": "new", + "active": true, + "ordering": 10, + "group": "new" + }, + "complete": { + "name": "Выполнен", + "code": "complete", + "active": true, + "ordering": 10, + "group": "complete" + }, + "availability-confirmed": { + "name": "Наличие подтверждено", + "code": "availability-confirmed", + "active": true, + "ordering": 10, + "group": "approval" + }, + "offer-analog": { + "name": "Предложить замену", + "code": "offer-analog", + "active": true, + "ordering": 20, + "group": "approval" + }, + "client-confirmed": { + "name": "Согласовано с клиентом", + "code": "client-confirmed", + "active": true, + "ordering": 30, + "group": "approval" + }, + "prepayed": { + "name": "Предоплата поступила", + "code": "prepayed", + "active": true, + "ordering": 40, + "group": "approval" + }, + "send-to-assembling": { + "name": "Передано в комплектацию", + "code": "send-to-assembling", + "active": true, + "ordering": 10, + "group": "assembling" + }, + "assembling": { + "name": "Комплектуется", + "code": "assembling", + "active": true, + "ordering": 20, + "group": "assembling" + }, + "assembling-complete": { + "name": "Укомплектован", + "code": "assembling-complete", + "active": true, + "ordering": 30, + "group": "assembling" + }, + "send-to-delivery": { + "name": "Передан в доставку", + "code": "send-to-delivery", + "active": true, + "ordering": 10, + "group": "delivery" + }, + "delivering": { + "name": "Доставляется", + "code": "delivering", + "active": true, + "ordering": 20, + "group": "delivery" + }, + "redirect": { + "name": "Доставка перенесена", + "code": "redirect", + "active": true, + "ordering": 30, + "group": "delivery" + }, + "no-call": { + "name": "Недозвон", + "code": "no-call", + "active": true, + "ordering": 10, + "group": "cancel" + }, + "no-product": { + "name": "Нет в наличии", + "code": "no-product", + "active": true, + "ordering": 20, + "group": "cancel" + }, + "already-buyed": { + "name": "Купил в другом месте", + "code": "already-buyed", + "active": true, + "ordering": 30, + "group": "cancel" + }, + "delyvery-did-not-suit": { + "name": "Не устроила доставка", + "code": "delyvery-did-not-suit", + "active": true, + "ordering": 40, + "group": "cancel" + }, + "prices-did-not-suit": { + "name": "Не устроила цена", + "code": "prices-did-not-suit", + "active": true, + "ordering": 50, + "group": "cancel" + }, + "cancel-other": { + "name": "Отменен", + "code": "cancel-other", + "active": true, + "ordering": 60, + "group": "cancel" + }, + "testdubl": { + "name": "testDubl", + "code": "testdubl", + "active": true, + "ordering": 990, + "group": "cancel" + }, + "test": { + "name": "test", + "code": "test", + "active": true, + "ordering": 990, + "group": "assembling" + }, + "dict-statuses-1571123798": { + "name": "Bbbdict-statuses-1571123798", + "code": "dict-statuses-1571123798", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1571123854": { + "name": "Bbbdict-statuses-1571123854", + "code": "dict-statuses-1571123854", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1571124864": { + "name": "Bbbdict-statuses-1571124864", + "code": "dict-statuses-1571124864", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1571124921": { + "name": "Bbbdict-statuses-1571124921", + "code": "dict-statuses-1571124921", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1571134107": { + "name": "Bbbdict-statuses-1571134107", + "code": "dict-statuses-1571134107", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1571134216": { + "name": "Bbbdict-statuses-1571134216", + "code": "dict-statuses-1571134216", + "active": false, + "ordering": 990, + "group": "new" + }, + "abandoned-cart": { + "name": "Брошенная корзина", + "code": "abandoned-cart", + "active": true, + "ordering": 990, + "group": "cancel" + }, + "dict-statuses-1575878862": { + "name": "Bbbdict-statuses-1575878862", + "code": "dict-statuses-1575878862", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1575878966": { + "name": "Bbbdict-statuses-1575878966", + "code": "dict-statuses-1575878966", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1581413644": { + "name": "Bbbdict-statuses-1581413644", + "code": "dict-statuses-1581413644", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1581413648": { + "name": "Bbbdict-statuses-1581413648", + "code": "dict-statuses-1581413648", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1581413651": { + "name": "Bbbdict-statuses-1581413651", + "code": "dict-statuses-1581413651", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1581413656": { + "name": "Bbbdict-statuses-1581413656", + "code": "dict-statuses-1581413656", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1581413772": { + "name": "Bbbdict-statuses-1581413772", + "code": "dict-statuses-1581413772", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1581413778": { + "name": "Bbbdict-statuses-1581413778", + "code": "dict-statuses-1581413778", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1581413782": { + "name": "Bbbdict-statuses-1581413782", + "code": "dict-statuses-1581413782", + "active": false, + "ordering": 990, + "group": "new" + }, + "dict-statuses-1581413790": { + "name": "Bbbdict-statuses-1581413790", + "code": "dict-statuses-1581413790", + "active": false, + "ordering": 990, + "group": "new" + }, + "delivered": { + "name": "Доставлен", + "code": "delivered", + "active": true, + "ordering": 40, + "group": "delivery" + } + } +} +EOF; + + $mock = static::createApiMockBuilder('reference/statuses'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->statuses(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testStatusesEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new Status(); + $entity->name = "Test Status"; + $entity->active = true; + $entity->ordering = 990; + $entity->group = "assembling"; + + $request = new StatusesEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/statuses/test/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->statusesEdit('test', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testStores(): void + { + $json = <<<'EOF' +{ + "success": true, + "stores": [ + { + "description": "Москва", + "type": "store-type-warehouse", + "inventoryType": "integer", + "address": { + "countryIso": "RU", + "region": "Москва город", + "regionId": 55, + "city": "Москва", + "cityId": 4995, + "cityType": "г.", + "street": "Ивовая", + "streetId": 1777155, + "streetType": "ул.", + "building": "23", + "text": "ул. Ивовая, д. 23", + "coordinates": { + "latitude": 51.660781, + "longitude": 39.200269 + } + }, + "active": true, + "phone": { + + }, + "code": "main12", + "workTime": { + "mo": [ + + ], + "tu": [ + + ], + "we": [ + + ], + "th": [ + + ], + "fr": [ + + ], + "sa": [ + + ], + "su": [ + + ] + }, + "name": "Основной" + }, + { + "description": "Санкт-Петербург", + "type": "store-type-warehouse", + "inventoryType": "available", + "address": { + "index": "196626", + "countryIso": "RU", + "region": "Санкт-Петербург город", + "regionId": 48, + "city": "Санкт-Петербург", + "cityId": 5000, + "cityType": "г.", + "streetType": "ул." + }, + "active": true, + "phone": { + + }, + "code": "food", + "workTime": { + "mo": [ + + ], + "tu": [ + + ], + "we": [ + + ], + "th": [ + + ], + "fr": [ + + ], + "sa": [ + + ], + "su": [ + + ] + }, + "name": "Продуктовый" + }, + { + "description": "Тверь", + "email": "luchshe@pozvonit.chemukogotozanimat", + "type": "store-type-warehouse", + "inventoryType": "integer", + "address": { + "index": "170040", + "countryIso": "RU", + "region": "Тверская область", + "regionId": 74, + "city": "Тверь", + "cityId": 4833, + "cityType": "г.", + "street": "ул. 2-я Переволоцкая", + "building": "50", + "flat": "13", + "house": "5", + "housing": "1", + "text": "ул. 2-я Переволоцкая, д. 50, стр. 5, корп. 1, кв./офис 13" + }, + "active": true, + "phone": { + "number": "88005553535" + }, + "code": "tver", + "workTime": { + "mo": [ + + ], + "tu": [ + + ], + "we": [ + + ], + "th": [ + + ], + "fr": [ + + ], + "sa": [ + + ], + "su": [ + + ] + }, + "name": "Тверь" + }, + { + "type": "store-type-warehouse", + "inventoryType": "integer", + "address": { + "countryIso": "RU", + "region": "Адыгея Республика", + "regionId": 26, + "city": "Майкоп", + "cityId": 1, + "cityType": "г.", + "street": "Ленина", + "streetId": 961854, + "streetType": "ул.", + "building": "50", + "flat": "13", + "house": "1", + "housing": "2", + "text": "ул. Ленина, д. 50, стр. 1, корп. 2, кв./офис 13" + }, + "active": true, + "phone": { + + }, + "code": "antisklad", + "workTime": { + "mo": [ + + ], + "tu": [ + + ], + "we": [ + + ], + "th": [ + + ], + "fr": [ + + ], + "sa": [ + + ], + "su": [ + + ] + }, + "name": "АнтиСклад" + }, + { + "type": "store-type-warehouse", + "inventoryType": "integer", + "address": { + "countryIso": "RU", + "region": "Москва город", + "regionId": 55, + "city": "Москва", + "cityId": 4995, + "cityType": "г.", + "street": "Кастанаевская", + "streetId": 1780121, + "streetType": "ул.", + "building": "48", + "flat": "30", + "metro": "Славянский бульвар/Пионерская", + "text": "ул. Кастанаевская, д. 48, кв./офис 30, метро Славянский бульвар/Пионерская" + }, + "active": true, + "phone": { + + }, + "code": "castan", + "workTime": { + "mo": [ + + ], + "tu": [ + + ], + "we": [ + + ], + "th": [ + + ], + "fr": [ + + ], + "sa": [ + + ], + "su": [ + + ] + }, + "name": "Кастанаевская" + }, + { + "inventoryType": "integer", + "address": { + + }, + "active": true, + "code": "test-store-v4", + "workTime": { + "mo": [ + + ], + "tu": [ + + ], + "we": [ + + ], + "th": [ + + ], + "fr": [ + + ], + "sa": [ + + ], + "su": [ + + ] + }, + "name": "Test Store V4" + }, + { + "type": "store-type-warehouse", + "inventoryType": "integer", + "address": { + "countryIso": "RU" + }, + "active": true, + "phone": { + + }, + "code": "test-store-v5", + "workTime": { + "mo": [ + + ], + "tu": [ + + ], + "we": [ + + ], + "th": [ + + ], + "fr": [ + + ], + "sa": [ + + ], + "su": [ + + ] + }, + "name": "Test Store V5" + }, + { + "type": "store-type-warehouse", + "inventoryType": "integer", + "address": { + "countryIso": "UA", + "region": "Винницкая область", + "regionId": 104, + "city": "Винница", + "cityId": 16054, + "cityType": "г.", + "street": "Аграрна", + "streetId": 1814071, + "streetType": "ул.", + "building": "12", + "text": "ул. Аграрна, д. 12" + }, + "active": true, + "phone": { + + }, + "code": "weiser_store", + "workTime": { + "mo": [ + + ], + "tu": [ + + ], + "we": [ + + ], + "th": [ + + ], + "fr": [ + + ], + "sa": [ + + ], + "su": [ + + ] + }, + "name": "weiser store" + }, + { + "type": "store-type-warehouse", + "inventoryType": "integer", + "address": { + "countryIso": "RU", + "region": "Ростовская область", + "regionId": 73, + "city": "Ростов-на-Дону", + "cityId": 4298, + "cityType": "г." + }, + "active": true, + "phone": { + + }, + "code": "rostov", + "workTime": { + "mo": [ + + ], + "tu": [ + + ], + "we": [ + + ], + "th": [ + + ], + "fr": [ + + ], + "sa": [ + + ], + "su": [ + + ] + }, + "name": "Ростов" + }, + { + "description": "Калининград", + "type": "store-type-warehouse", + "inventoryType": "integer", + "address": { + "countryIso": "RU", + "region": "Калининградская область", + "regionId": 30, + "city": "Калининград", + "cityId": 2798, + "cityType": "г.", + "street": "9 Апреля", + "streetId": 1341484, + "streetType": "ул.", + "building": "5", + "text": "ул. 9 Апреля, д. 5" + }, + "active": true, + "phone": { + + }, + "code": "klgd", + "workTime": { + "mo": [ + + ], + "tu": [ + + ], + "we": [ + + ], + "th": [ + + ], + "fr": [ + + ], + "sa": [ + + ], + "su": [ + + ] + }, + "name": "Калининград" + } + ] +} +EOF; + + $mock = static::createApiMockBuilder('reference/stores'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->stores(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testStoresEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new Store(); + $entity->address = new StoreAddress(); + $entity->address->countryIso = "UA"; + $entity->address->region = "Винницкая область"; + $entity->address->regionId = 104; + $entity->address->city = "Винница"; + $entity->address->cityId = 16054; + $entity->address->cityType = "г."; + $entity->address->street = "Аграрна"; + $entity->address->streetId = 1814071; + $entity->address->streetType = "ул."; + $entity->address->building = "12"; + $entity->address->text = "ул. Аграрна, д. 12"; + $entity->workTime = new SerializedStoreWeekOpeningHours( + [new StoreWorkTime('9:00', '18:00', '12:00', '13:00')], + [new StoreWorkTime('9:00', '18:00', '12:00', '13:00')], + [new StoreWorkTime('9:00', '18:00', '12:00', '13:00')], + [new StoreWorkTime('9:00', '18:00', '12:00', '13:00')], + [new StoreWorkTime('9:00', '18:00', '12:00', '13:00')] + ); + $entity->type = StoreType::STORE_TYPE_WAREHOUSE; + $entity->inventoryType = StoreInventoryType::INTEGER; + $entity->active = true; + $entity->phone = new StorePhone('88005553123'); + $entity->name = 'Test Store'; + + $request = new StoresEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/stores/test/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->storesEdit('test', $request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testUnits(): void + { + $json = <<<'EOF' +{ + "success": true, + "units": { + "6": { + "code": "6", + "name": "Метр", + "sym": "м", + "default": false, + "active": true + }, + "112": { + "code": "112", + "name": "Литр", + "sym": "л.", + "default": false, + "active": true + }, + "163": { + "code": "163", + "name": "Грамм", + "sym": "г", + "default": false, + "active": true + }, + "166": { + "code": "166", + "name": "Килограмм", + "sym": "кг", + "default": false, + "active": true + }, + "366": { + "code": "366", + "name": "Год", + "sym": "г; ле", + "default": false, + "active": true + }, + "796": { + "code": "796", + "name": "Штука", + "sym": "шт", + "default": false, + "active": true + }, + "797": { + "code": "797", + "name": "пог. м", + "sym": "пог. ", + "default": false, + "active": true + }, + "798": { + "code": "798", + "name": "упак 10 шт", + "sym": "упак ", + "default": false, + "active": true + }, + "799": { + "code": "799", + "name": "упак", + "sym": "упак", + "default": false, + "active": true + }, + "800": { + "code": "800", + "name": "шт (2 пог. м)", + "sym": "шт (2", + "default": false, + "active": true + }, + "801": { + "code": "801", + "name": "компл", + "sym": "компл", + "default": false, + "active": true + }, + "802": { + "code": "802", + "name": "пар", + "sym": "пар", + "default": false, + "active": true + }, + "803": { + "code": "803", + "name": "бухта (10 рул)", + "sym": "бухта", + "default": false, + "active": true + }, + "805": { + "code": "805", + "name": "кор.", + "sym": "кор.", + "default": false, + "active": true + }, + "806": { + "code": "806", + "name": "набор", + "sym": "набор", + "default": false, + "active": true + }, + "808": { + "code": "808", + "name": "шт (2 м)", + "sym": "шт (2", + "default": false, + "active": true + }, + "809": { + "code": "809", + "name": "м2", + "sym": "м2", + "default": false, + "active": true + }, + "810": { + "code": "810", + "name": "упак (10 шт)", + "sym": "упак ", + "default": false, + "active": true + }, + "811": { + "code": "811", + "name": "бухта (15 м)", + "sym": "бухта", + "default": false, + "active": true + }, + "812": { + "code": "812", + "name": "бухта (25 м)", + "sym": "бухта", + "default": false, + "active": true + }, + "813": { + "code": "813", + "name": "бухта (20 м)", + "sym": "бухта", + "default": false, + "active": true + }, + "815": { + "code": "815", + "name": "шт (4 пог. м)", + "sym": "шт (4", + "default": false, + "active": true + }, + "816": { + "code": "816", + "name": "бухта (100 м)", + "sym": "бухта", + "default": false, + "active": true + }, + "817": { + "code": "817", + "name": "бухта (50 м)", + "sym": "бухта", + "default": false, + "active": true + }, + "818": { + "code": "818", + "name": "упак (5 кг)", + "sym": "упак ", + "default": false, + "active": true + }, + "819": { + "code": "819", + "name": "бухта (50 пог. м)", + "sym": "бухта", + "default": false, + "active": true + }, + "820": { + "code": "820", + "name": "бухта (30 пог. м)", + "sym": "бухта", + "default": false, + "active": true + }, + "821": { + "code": "821", + "name": "бухта (25 пог. м)", + "sym": "бухта", + "default": false, + "active": true + }, + "824": { + "code": "824", + "name": "упак (2 шт)", + "sym": "упак ", + "default": false, + "active": true + }, + "825": { + "code": "825", + "name": "бухта", + "sym": "бухта", + "default": false, + "active": true + }, + "830": { + "code": "830", + "name": "пакет", + "sym": "пакет", + "default": false, + "active": true + }, + "831": { + "code": "831", + "name": "рул", + "sym": "рул", + "default": false, + "active": true + }, + "832": { + "code": "832", + "name": "банка", + "sym": "банка", + "default": false, + "active": true + }, + "833": { + "code": "833", + "name": "ведро", + "sym": "ведро", + "default": false, + "active": true + }, + "834": { + "code": "834", + "name": "бут", + "sym": "бут", + "default": false, + "active": true + }, + "838": { + "code": "838", + "name": "упак (5 шт)", + "sym": "упак ", + "default": false, + "active": true + }, + "843": { + "code": "843", + "name": "м2 (3,99 шт)", + "sym": "м2 (3", + "default": false, + "active": true + }, + "844": { + "code": "844", + "name": "элем", + "sym": "элем", + "default": false, + "active": true + }, + "847": { + "code": "847", + "name": "м2 (4,048 шт)", + "sym": "м2 (4", + "default": false, + "active": true + }, + "849": { + "code": "849", + "name": "Звено (элемент)", + "sym": "Звено", + "default": false, + "active": true + }, + "850": { + "code": "850", + "name": "куль.", + "sym": "куль.", + "default": false, + "active": true + }, + "852": { + "code": "852", + "name": "меш.", + "sym": "меш.", + "default": false, + "active": true + }, + "853": { + "code": "853", + "name": "шт (3 пог. м)", + "sym": "шт (3", + "default": false, + "active": true + }, + "862": { + "code": "862", + "name": "шт (2,2 пог. м)", + "sym": "шт (2", + "default": false, + "active": true + }, + "865": { + "code": "865", + "name": "шт (0,016 м3)", + "sym": "шт (0", + "default": false, + "active": true + }, + "867": { + "code": "867", + "name": "бутыль", + "sym": "бутыл", + "default": false, + "active": true + }, + "869": { + "code": "869", + "name": "шт (75 м2)", + "sym": "шт (7", + "default": false, + "active": true + }, + "871": { + "code": "871", + "name": "упак (4 шт)", + "sym": "упак ", + "default": false, + "active": true + }, + "873": { + "code": "873", + "name": "шт (2,5 пог. м)", + "sym": "шт (2", + "default": false, + "active": true + }, + "874": { + "code": "874", + "name": "рул (25 м2)", + "sym": "рул (", + "default": false, + "active": true + }, + "875": { + "code": "875", + "name": "м2 (4,055 шт)", + "sym": "м2 (4", + "default": false, + "active": true + }, + "876": { + "code": "876", + "name": "кан", + "sym": "кан", + "default": false, + "active": true + }, + "884": { + "code": "884", + "name": "упак (3 шт)", + "sym": "упак ", + "default": false, + "active": true + }, + "886": { + "code": "886", + "name": "т.", + "sym": "т.", + "default": false, + "active": true + }, + "887": { + "code": "887", + "name": "м2 (4,047 шт)", + "sym": "м2 (4", + "default": false, + "active": true + }, + "891": { + "code": "891", + "name": "шт (2,06 пог. м)", + "sym": "шт (2", + "default": false, + "active": true + }, + "899": { + "code": "899", + "name": "шт (0,047 м3)", + "sym": "шт (0", + "default": false, + "active": true + }, + "900": { + "code": "900", + "name": "рул (0,2 шт)", + "sym": "рул (", + "default": false, + "active": true + }, + "910": { + "code": "910", + "name": "рул (75 м2)", + "sym": "рул (", + "default": false, + "active": true + }, + "912": { + "code": "912", + "name": "шт (0,045 м3)", + "sym": "шт (0", + "default": false, + "active": true + }, + "915": { + "code": "915", + "name": "упак (6 шт)", + "sym": "упак ", + "default": false, + "active": true + }, + "1429": { + "code": "1429", + "name": "шт (1 упак)", + "sym": "шт (1", + "default": false, + "active": true + }, + "1712": { + "code": "1712", + "name": "упак (10 кг)", + "sym": "упак ", + "default": false, + "active": true + }, + "1761": { + "code": "1761", + "name": "рул (40м2)", + "sym": "рул (", + "default": false, + "active": true + }, + "2203": { + "code": "2203", + "name": "пог. м (0,125 рул)", + "sym": "пог. ", + "default": false, + "active": true + }, + "2204": { + "code": "2204", + "name": "пачка", + "sym": "пачка", + "default": false, + "active": true + }, + "2543": { + "code": "2543", + "name": "упак (5,25 м2)", + "sym": "упак ", + "default": false, + "active": true + }, + "2544": { + "code": "2544", + "name": "упак (8,4 м2)", + "sym": "упак ", + "default": false, + "active": true + }, + "2558": { + "code": "2558", + "name": "пог. м (2 м)", + "sym": "пог. ", + "default": false, + "active": true + }, + "2559": { + "code": "2559", + "name": "упак (6 м2)", + "sym": "упак ", + "default": false, + "active": true + }, + "2568": { + "code": "2568", + "name": "л. (9 кг)", + "sym": "л. (9", + "default": false, + "active": true + }, + "2576": { + "code": "2576", + "name": "упак (0,84 м2)", + "sym": "упак ", + "default": false, + "active": true + }, + "2579": { + "code": "2579", + "name": "рул (50 м2)", + "sym": "рул (", + "default": false, + "active": true + }, + "2582": { + "code": "2582", + "name": "упак (5 м2)", + "sym": "упак ", + "default": false, + "active": true + }, + "2589": { + "code": "2589", + "name": "рул (16,5 м2)", + "sym": "рул (", + "default": false, + "active": true + }, + "2590": { + "code": "2590", + "name": "шт (0,107 м2)", + "sym": "шт (0", + "default": false, + "active": true + }, + "2591": { + "code": "2591", + "name": "пог. м (0,5 м2)", + "sym": "пог. ", + "default": false, + "active": true + }, + "2592": { + "code": "2592", + "name": "пог. м (0,35 м2)", + "sym": "пог. ", + "default": false, + "active": true + }, + "2593": { + "code": "2593", + "name": "пог. м (0,25 м2)", + "sym": "пог. ", + "default": false, + "active": true + }, + "2595": { + "code": "2595", + "name": "бухта (10 м)", + "sym": "бухта", + "default": false, + "active": true + }, + "2599": { + "code": "2599", + "name": "упак (0,76 пог. м)", + "sym": "упак ", + "default": false, + "active": true + }, + "2600": { + "code": "2600", + "name": "упак (1,04 м2)", + "sym": "упак ", + "default": false, + "active": true + }, + "2602": { + "code": "2602", + "name": "упак (1,2 пог. м)", + "sym": "упак ", + "default": false, + "active": true + }, + "2604": { + "code": "2604", + "name": "упак (0,79 м2)", + "sym": "упак ", + "default": false, + "active": true + }, + "2609": { + "code": "2609", + "name": "м (0,01 бухта)", + "sym": "м (0,", + "default": false, + "active": true + }, + "2610": { + "code": "2610", + "name": "упак (1,08 пог. м)", + "sym": "упак ", + "default": false, + "active": true + }, + "2611": { + "code": "2611", + "name": "упак (0,98 пог. м)", + "sym": "упак ", + "default": false, + "active": true + }, + "2612": { + "code": "2612", + "name": "упак (1,05 пог. м)", + "sym": "упак ", + "default": false, + "active": true + }, + "2613": { + "code": "2613", + "name": "упак (1,5 пог. м)", + "sym": "упак ", + "default": false, + "active": true + }, + "2629": { + "code": "2629", + "name": "упак (0,57 м2)", + "sym": "упак ", + "default": false, + "active": true + }, + "2637": { + "code": "2637", + "name": "бухта(25 пог.м.)", + "sym": "бухта", + "default": false, + "active": true + }, + "2653": { + "code": "2653", + "name": "упак (5 м)", + "sym": "упак ", + "default": false, + "active": true + }, + "pc": { + "code": "pc", + "name": "Штука", + "sym": "шт.", + "default": true, + "active": true + }, + "kg": { + "code": "kg", + "name": "Килограмм", + "sym": "кг", + "default": false, + "active": true + }, + "m": { + "code": "m", + "name": "Метр", + "sym": "м", + "default": false, + "active": true + }, + "l": { + "code": "l", + "name": "Литр", + "sym": "л.", + "default": false, + "active": true + }, + "pkg": { + "code": "pkg", + "name": "Упаковка", + "sym": "упак.", + "default": false, + "active": true + }, + "pcs": { + "code": "pcs", + "name": "Штука", + "sym": "шт.", + "default": false, + "active": true + }, + "nuqbm77fiiEsB0aZNoiik2": { + "code": "nuqbm77fiiEsB0aZNoiik2", + "name": "azgalot", + "sym": "azgal", + "default": false, + "active": true + }, + "pce": { + "code": "pce", + "name": "Штука", + "sym": "шт", + "default": false, + "active": true + }, + "nmp": { + "code": "nmp", + "name": "Упаковка", + "sym": "упак", + "default": false, + "active": true + }, + "kgm": { + "code": "kgm", + "name": "Килограмм", + "sym": "кг", + "default": false, + "active": true + }, + "mtr": { + "code": "mtr", + "name": "Метр", + "sym": "м", + "default": false, + "active": true + }, + "nbb": { + "code": "nbb", + "name": "Бобина", + "sym": "боб", + "default": false, + "active": true + }, + "npl": { + "code": "npl", + "name": "Рулон", + "sym": "рул", + "default": false, + "active": true + }, + "azg": { + "code": "azg", + "name": "azgalot", + "sym": "azg", + "default": false, + "active": true + }, + "cen": { + "code": "cen", + "name": "100 штук", + "sym": "100шт", + "default": false, + "active": true + } + } +} +EOF; + + $mock = static::createApiMockBuilder('reference/units'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->units(); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testUnitsEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $entity = new SerializedUnit(); + $entity->name = "Бобина"; + $entity->sym = "боб"; + $entity->active = true; + + $request = new UnitsEditRequest($entity); + + $mock = static::createApiMockBuilder('reference/units/nbb/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->references->unitsEdit('nbb', $request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/SegmentsTest.php b/tests/src/ResourceGroup/SegmentsTest.php new file mode 100644 index 0000000..069c0c9 --- /dev/null +++ b/tests/src/ResourceGroup/SegmentsTest.php @@ -0,0 +1,122 @@ +filter = new SegmentApiFilter(); + $request->filter->active = NumericBoolean::TRUE; + $request->filter->minCustomersCount = 700; + + $mock = static::createApiMockBuilder('segments'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(static::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->segments->list($request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/SettingsTest.php b/tests/src/ResourceGroup/SettingsTest.php new file mode 100644 index 0000000..aa05dd2 --- /dev/null +++ b/tests/src/ResourceGroup/SettingsTest.php @@ -0,0 +1,56 @@ +matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->settings->get(); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/StatisticsTest.php b/tests/src/ResourceGroup/StatisticsTest.php new file mode 100644 index 0000000..8de0eff --- /dev/null +++ b/tests/src/ResourceGroup/StatisticsTest.php @@ -0,0 +1,42 @@ +matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->statistics->update(); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/StoreTest.php b/tests/src/ResourceGroup/StoreTest.php new file mode 100644 index 0000000..bdd1599 --- /dev/null +++ b/tests/src/ResourceGroup/StoreTest.php @@ -0,0 +1,621 @@ +filter = new InventoryFilterType(); + $request->filter->productActive = NumericBoolean::TRUE; + $request->filter->sites = ['moysklad', 'aliexpress']; + + $mock = static::createApiMockBuilder('store/inventories'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(self::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->store->inventories($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testInventoriesUpload(): void + { + $json = <<<'EOF' +{ + "success": true, + "processedOffersCount": 1 +} +EOF; + $offer = new Offer(); + $offer->xmlId = '1'; + $offer->stores = [new Inventory('main12', 15, 15)]; + + $request = new InventoriesUploadRequest(); + $request->offers = [$offer]; + $request->site = 'aliexpress'; + + $mock = static::createApiMockBuilder('store/inventories/upload'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->store->inventoriesUpload($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testPricesUpload(): void + { + $json = <<<'EOF' +{ + "success": true, + "processedOffersCount": 1, + "notFoundOffers": [] +} +EOF; + $price = new PriceUploadInput(); + $price->site = 'aliexpress'; + $price->xmlId = '1'; + $price->prices = [new PriceUploadPricesInput('base', 100)]; + + $mock = static::createApiMockBuilder('store/prices/upload'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm(new PricesUploadRequest([$price]))) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->store->pricesUpload(new PricesUploadRequest([$price])); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testProductGroups(): void + { + $json = <<<'EOF' +{ + "success": true, + "pagination": { + "limit": 20, + "totalCount": 7, + "currentPage": 1, + "totalPageCount": 1 + }, + "productGroup": [ + { + "site": "aliexpress", + "id": 4326, + "name": "Test Category", + "externalId": "1", + "active": true + }, + { + "site": "moysklad", + "id": 3676, + "name": "warehouseRoot", + "externalId": "warehouseRoot", + "active": true + }, + { + "site": "moysklad", + "id": 3677, + "name": "Вторая группа", + "externalId": "2group", + "active": true + }, + { + "parentId": 3677, + "site": "moysklad", + "id": 3679, + "name": "Входящая в группу", + "externalId": "2-1Group", + "active": true + }, + { + "parentId": 3679, + "site": "moysklad", + "id": 3680, + "name": "test", + "externalId": "test", + "active": true + }, + { + "site": "moysklad", + "id": 3724, + "name": "Услуги", + "externalId": "fFkaMR23j9nCbAMnkG2T20", + "active": true + }, + { + "site": "moysklad", + "id": 3678, + "name": "Фрукты", + "externalId": "extFruits", + "active": true + } + ] +} +EOF; + + $request = new ProductGroupsRequest(); + $request->filter = new ProductGroupFilterType(); + $request->filter->sites = ['moysklad', 'aliexpress']; + $request->filter->active = NumericBoolean::TRUE; + + $mock = static::createApiMockBuilder('store/product-groups'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(self::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->store->productGroups($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testProducts(): void + { + $json = <<<'EOF' +{ + "success": true, + "pagination": { + "limit": 20, + "totalCount": 1, + "currentPage": 1, + "totalPageCount": 1 + }, + "products": [ + { + "minPrice": 624, + "maxPrice": 624, + "id": 828272, + "article": "38311", + "name": "Test Product", + "url": "https://example.com", + "imageUrl": "https://example.com/image.jpg", + "description": "Test Description", + "groups": [ + { + "id": 4050, + "externalId": "368" + }, + { + "id": 4154, + "externalId": "391" + }, + { + "id": 4279, + "externalId": "394" + } + ], + "externalId": "38311", + "manufacturer": "Test", + "offers": [ + { + "name": "Test #1", + "price": 624, + "images": [ + "https://example.com/image.jpg" + ], + "id": 1941833, + "externalId": "38311", + "xmlId": "38311", + "article": "38311", + "prices": [ + { + "priceType": "base", + "price": 624, + "ordering": 991 + } + ], + "purchasePrice": 272.64, + "vatRate": "none", + "properties": { + "ves": "33", + "brend": "Test", + "image": "https://example.com/image.jpg", + "ves_g": "33", + "artikul": "38311" + }, + "quantity": 0, + "weight": 33, + "active": true, + "unit": { + "code": "pc", + "name": "Штука", + "sym": "шт." + } + } + ], + "active": true, + "quantity": 0, + "markable": false + } + ] +} +EOF; + + $request = new ProductsRequest(); + $request->filter = new ProductFilterType(); + $request->filter->active = NumericBoolean::TRUE; + $request->filter->priceType = 'base'; + $request->filter->maxPrice = '10000'; + $request->filter->name = 'Test Product'; + + $mock = static::createApiMockBuilder('store/products'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(self::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->store->products($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testProductsSinceUpdated(): void + { + $json = <<<'EOF' +{ + "success": true, + "pagination": { + "limit": 20, + "totalCount": 1, + "currentPage": 1, + "totalPageCount": 1 + }, + "products": [ + { + "updatedAt": "2019-12-01 00:00:00", + "minPrice": 624, + "maxPrice": 624, + "id": 828272, + "article": "38311", + "name": "Test Product", + "url": "https://example.com", + "imageUrl": "https://example.com/image.jpg", + "description": "Test Description", + "groups": [ + { + "id": 4050, + "externalId": "368" + }, + { + "id": 4154, + "externalId": "391" + }, + { + "id": 4279, + "externalId": "394" + } + ], + "externalId": "38311", + "manufacturer": "Test", + "offers": [ + { + "name": "Test #1", + "price": 624, + "images": [ + "https://example.com/image.jpg" + ], + "id": 1941833, + "externalId": "38311", + "xmlId": "38311", + "article": "38311", + "prices": [ + { + "priceType": "base", + "price": 624, + "ordering": 991 + } + ], + "purchasePrice": 272.64, + "vatRate": "none", + "properties": { + "ves": "33", + "brend": "Test", + "image": "https://example.com/image.jpg", + "ves_g": "33", + "artikul": "38311" + }, + "quantity": 0, + "weight": 33, + "active": true, + "unit": { + "code": "pc", + "name": "Штука", + "sym": "шт." + } + } + ], + "active": true, + "quantity": 0, + "markable": false + } + ] +} +EOF; + + $request = new ProductsRequest(); + $request->filter = new ProductFilterType(); + $request->filter->active = NumericBoolean::TRUE; + $request->filter->priceType = 'base'; + $request->filter->maxPrice = '10000'; + $request->filter->name = 'Test Product'; + $request->filter->sinceId = 828272; + $request->filter->sinceUpdatedAt = DateTimeTransformer::create('2020-01-01 00:00:00'); + + $mock = static::createApiMockBuilder('store/products'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(self::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->store->products($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testProductProperties(): void + { + $json = <<<'EOF' +{ + "success": true, + "pagination": { + "limit": 20, + "totalCount": 1, + "currentPage": 1, + "totalPageCount": 1 + }, + "properties": [ + { + "sites": [ + "e-mapper", + "sendpulse", + "glavpunkt", + "retailcrm-services-peshkariki", + "vk-com", + "moysklad", + "eftestshop-ru" + ], + "groups": [ + { + "id": 3676, + "name": "warehouseRoot" + }, + { + "id": 3679, + "name": "Входящая в группу" + }, + { + "id": 3680, + "name": "test" + }, + { + "id": 3724, + "name": "Услуги" + } + ], + "code": "code", + "name": "Код", + "isNumeric": false + } + ] +} +EOF; + + $request = new ProductPropertiesRequest(); + $request->filter = new ProductPropertiesFilterType(); + $request->filter->sites = ['moysklad', 'aliexpress']; + + $mock = static::createApiMockBuilder('store/products/properties'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(self::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->store->productsProperties($request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/TasksTest.php b/tests/src/ResourceGroup/TasksTest.php new file mode 100644 index 0000000..0856bd3 --- /dev/null +++ b/tests/src/ResourceGroup/TasksTest.php @@ -0,0 +1,178 @@ +filter = new TaskFilter(); + $request->filter->performers = [27]; + $request->filter->status = TaskStatus::COMPLETED; + + $mock = static::createApiMockBuilder('tasks'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(self::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->tasks->list($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCreate(): void + { + $json = <<<'EOF' +{ + "success": true, + "id": 1 +} +EOF; + + $request = new TasksCreateRequest(); + $request->task = new Task(); + $request->task->text = 'Test task #1'; + $request->task->performerId = 27; + $request->task->customer = new AbstractCustomer(null, 'ru1067815391', 'aliexpress'); + $request->site = 'aliexpress'; + + $mock = static::createApiMockBuilder('tasks/create'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->tasks->create($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testGet(): void + { + $json = <<<'EOF' +{ + "success": true, + "task": { + "id": 1, + "text": "Test task #1", + "createdAt": "2021-03-05 12:35:29", + "complete": false, + "performer": 27, + "performerType": "user", + "customer": { + "type": "customer", + "id": 4976, + "externalId": "ru1067815391", + "site": "aliexpress" + } + } +} +EOF; + + $mock = static::createApiMockBuilder('tasks/1'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->tasks->get(1); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testEdit(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $request = new TasksCreateRequest(); + $request->task = new Task(); + $request->task->text = 'Test task #1 (edited)'; + $request->site = 'aliexpress'; + + $mock = static::createApiMockBuilder('tasks/1/edit'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->tasks->edit(1, $request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/TelephonyTest.php b/tests/src/ResourceGroup/TelephonyTest.php new file mode 100644 index 0000000..84eee1e --- /dev/null +++ b/tests/src/ResourceGroup/TelephonyTest.php @@ -0,0 +1,145 @@ +site = 'aliexpress'; + $event->type = CallEventType::IN; + $event->phone = '88005553125'; + $event->userIds = [27]; + + $request = new TelephonyCallEventRequest($event); + + $mock = static::createApiMockBuilder('telephony/call/event'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->telephony->callEvent($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testCallsUpload(): void + { + $json = <<<'EOF' +{ + "success": true, + "processedCallsCount": 1 +} +EOF; + + $call = new Call(); + $call->externalId = 'test_call_external_id'; + $call->date = new DateTime(); + $call->type = CallEventType::IN; + $call->phone = '88005553125'; + $call->userId = 27; + $call->result = CallResult::ANSWERED; + $call->duration = 60; + $call->durationWaiting = 10; + $call->recordUrl = 'https://examle.com/test.mp3'; + $call->site = 'aliexpress'; + + $request = new TelephonyCallsUploadRequest([$call]); + + $mock = static::createApiMockBuilder('telephony/calls/upload'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->telephony->callsUpload($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testManager(): void + { + $json = <<<'EOF' +{ + "success": true, + "manager": { + "id": 1, + "firstName": "John", + "lastName": "John", + "patronymic": "John", + "email": "test@examle.com", + "code": "12" + }, + "customer": { + "id": 1, + "externalId": "1", + "firstName": "John", + "lastName": "John", + "patronymic": "John", + "email": "test@examle.com", + "phones": [ + { + "number": "88005553125" + } + ] + }, + "links": { + "newOrderLink": "https://azgalot.retailcrm.ru/orders/add?phone=88005553125", + "newCustomerLink": "https://azgalot.retailcrm.ru/customers/add?phone=88005553125" + } +} +EOF; + + $request = new TelephonyManagerRequest(); + $request->phone = '88005553125'; + $request->details = NumericBoolean::TRUE; + $request->ignoreStatus = NumericBoolean::TRUE; + + $mock = static::createApiMockBuilder('telephony/manager'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(self::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->telephony->manager($request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/UsersTest.php b/tests/src/ResourceGroup/UsersTest.php new file mode 100644 index 0000000..87ae3e9 --- /dev/null +++ b/tests/src/ResourceGroup/UsersTest.php @@ -0,0 +1,259 @@ +page = 1; + $request->limit = PaginationLimit::LIMIT_20; + + $mock = static::createApiMockBuilder('user-groups'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(self::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->users->userGroups($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testList(): void + { + $json = <<<'EOF' +{ + "success": true, + "pagination": { + "limit": 20, + "totalCount": 8, + "currentPage": 1, + "totalPageCount": 1 + }, + "users": [ + { + "id": 28, + "createdAt": "2020-05-25 12:49:00", + "active": true, + "email": "programmer@retailcrm.ru", + "firstName": "programmer", + "status": "free", + "online": true, + "isAdmin": true, + "isManager": true, + "groups": [ + { + "id": 25, + "name": "Менеджеры", + "code": "manager" + }, + { + "id": 26, + "name": "Руководители", + "code": "director" + } + ], + "mgUserId": 305, + "senderEmail": "test@example.com", + "senderName": "test" + } + ] +} +EOF; + + $request = new UsersRequest(); + $request->filter = new ApiUserFilter(); + $request->filter->active = NumericBoolean::TRUE; + $request->filter->isAdmin = NumericBoolean::TRUE; + + $mock = static::createApiMockBuilder('users'); + $mock->matchMethod(RequestMethod::GET) + ->matchQuery(self::encodeFormArray($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->users->list($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testGet(): void + { + $json = <<<'EOF' +{ + "success": true, + "user": { + "id": 28, + "createdAt": "2020-05-25 12:49:00", + "active": true, + "email": "programmer@retailcrm.ru", + "firstName": "programmer", + "status": "free", + "online": true, + "isAdmin": true, + "isManager": true, + "groups": [ + { + "id": 25, + "name": "Менеджеры", + "code": "manager" + }, + { + "id": 26, + "name": "Руководители", + "code": "director" + } + ], + "mgUserId": 305, + "senderEmail": "test@example.com", + "senderName": "test" + } +} +EOF; + + $mock = static::createApiMockBuilder('users/28'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->users->get(28); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testStatus(): void + { + $json = <<<'EOF' +{ + "success": true +} +EOF; + + $request = new UsersStatusRequest(UserStatus::BUSY); + + $mock = static::createApiMockBuilder('users/28/status'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->users->status(28, $request); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/src/ResourceGroup/VerificationTest.php b/tests/src/ResourceGroup/VerificationTest.php new file mode 100644 index 0000000..3ec85e7 --- /dev/null +++ b/tests/src/ResourceGroup/VerificationTest.php @@ -0,0 +1,81 @@ +code = 'code'; + $confirm->checkId = 'checkId'; + + $request = new SmsVerificationConfirmRequest($confirm); + + $mock = static::createApiMockBuilder('verification/sms/confirm'); + $mock->matchMethod(RequestMethod::POST) + ->matchBody(self::encodeForm($request)) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->verification->smsConfirm($request); + + self::assertModelEqualsToResponse($json, $response); + } + + public function testSmsStatus(): void + { + $json = <<<'EOF' +{ + "success": true, + "verification": { + "createdAt": "2021-03-05 12:00:00", + "verifiedAt": "2021-03-05 12:00:00", + "checkId": "checkId", + "actionType": "register" + } +} +EOF; + + $mock = static::createApiMockBuilder('verification/sms/1/status'); + $mock->matchMethod(RequestMethod::GET) + ->reply(200) + ->withBody($json); + + $client = TestClientFactory::createClient($mock->getClient()); + $response = $client->verification->smsStatus('1'); + + self::assertModelEqualsToResponse($json, $response); + } +} diff --git a/tests/utils/ArrayLogger.php b/tests/utils/ArrayLogger.php new file mode 100644 index 0000000..755b2e4 --- /dev/null +++ b/tests/utils/ArrayLogger.php @@ -0,0 +1,60 @@ +messages; + } + + /** + * @return string[] + */ + public function getLastMessage(): array + { + return count($this->messages) > 0 ? end($this->messages) : []; + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + $this->messages[] = [ + 'level' => $level, + 'message' => $message, + 'context' => $context, + ]; + } +} diff --git a/tests/utils/ClientFactoryDependentService.php b/tests/utils/ClientFactoryDependentService.php new file mode 100644 index 0000000..1c3f31d --- /dev/null +++ b/tests/utils/ClientFactoryDependentService.php @@ -0,0 +1,79 @@ +clientFactory = $clientFactory; + } + + /** + * @param \Psr\Http\Client\ClientInterface|null $httpClient + * + * @return ClientFactoryDependentService + */ + public function setHttpClient(?ClientInterface $httpClient): ClientFactoryDependentService + { + $this->httpClient = $httpClient; + return $this; + } + + /** + * Returns true if API is available + * + * @param string $apiUrl + * @param string $apiKey + * + * @return bool + */ + public function isApiAccessible(string $apiUrl, string $apiKey): bool + { + try { + $client = $this->clientFactory->createClient($apiUrl, $apiKey); + + if (null !== $this->httpClient) { + ReflectionUtils::setProperty( + ReflectionUtils::getProperty($client, 'api'), + 'httpClient', + $this->httpClient + ); + } + + $client->api->apiVersions(); + + return true; + } catch (Throwable $throwable) { + return false; + } + } +} diff --git a/tests/utils/Exception/MatcherException.php b/tests/utils/Exception/MatcherException.php new file mode 100644 index 0000000..0abb9cb --- /dev/null +++ b/tests/utils/Exception/MatcherException.php @@ -0,0 +1,34 @@ +setCache(static::$cache) + ->build(); + + return (new ClientBuilder()) + ->setApiUrl(TestConfig::getApiUrl()) + ->setAuthenticatorHandler($authenticator ?? new HeaderAuthenticatorHandler(TestConfig::getApiKey())) + ->setDebugLogger($logger) + ->setEventDispatcher($eventDispatcher) + ->setFormEncoder($encoder) + ->setHttpClient($client) + ->build(); + } +} diff --git a/tests/utils/ReflectionUtils.php b/tests/utils/ReflectionUtils.php new file mode 100644 index 0000000..582377c --- /dev/null +++ b/tests/utils/ReflectionUtils.php @@ -0,0 +1,62 @@ +getValue($object); + } + + /** + * @param object $object + * @param string $property + * @param mixed $value + * + * @throws \ReflectionException + */ + public static function setProperty(object $object, string $property, $value): void + { + static::getReflectionProperty($object, $property)->setValue($object, $value); + } + + /** + * @param object $object + * @param string $property + * + * @return \ReflectionProperty + * @throws \ReflectionException + */ + private static function getReflectionProperty(object $object, string $property): ReflectionProperty + { + $prop = new ReflectionProperty($object, $property); + $prop->setAccessible(true); + + return $prop; + } +} diff --git a/tests/utils/TestCase/AbstractApiResourceGroupTestCase.php b/tests/utils/TestCase/AbstractApiResourceGroupTestCase.php new file mode 100644 index 0000000..737952c --- /dev/null +++ b/tests/utils/TestCase/AbstractApiResourceGroupTestCase.php @@ -0,0 +1,330 @@ +matchScheme((string) parse_url(TestConfig::getApiUrl(), PHP_URL_SCHEME)) + ->matchHost((string) parse_url(TestConfig::getApiUrl(), PHP_URL_HOST)) + ->matchHeader('X-Api-Key', TestConfig::getApiKey()) + ->matchPath( + (empty($testDataPath) || '/' === $testDataPath) + ? '/api/v5/' . $pathFragment + : trim($testDataPath, '/') . '/' . $pathFragment + ); + } + + /** + * @param int $code + * @param object|mixed[]|string $response + * + * @return \Psr\Http\Message\ResponseInterface + * @throws InvalidArgumentException + */ + protected static function responseJson(int $code, $response): ResponseInterface + { + $data = null; + + switch (gettype($response)) { + case 'string': + $data = static::getStreamFactory()->createStream((string) $response); + break; + case 'array': + case 'object': + $data = static::getStreamFactory()->createStream(static::getSerializer()->serialize($response, 'json')); + break; + default: + throw new InvalidArgumentException(sprintf( + 'Expected string, object, or array, got "%s"', + gettype($response) + )); + } + + return static::getResponseFactory()->createResponse($code) + ->withHeader('Content-Type', 'application/json') + ->withBody($data); + } + + /** + * @param string $path + * @param bool $header + * + * @return \Pock\PockBuilder + */ + protected static function createUnversionedApiMockBuilder(string $path = '', bool $header = true): PockBuilder + { + $testDataPath = parse_url(TestConfig::getApiUrl(), PHP_URL_PATH); + + $builder = (new PockBuilder()) + ->matchScheme((string) parse_url(TestConfig::getApiUrl(), PHP_URL_SCHEME)) + ->matchHost((string) parse_url(TestConfig::getApiUrl(), PHP_URL_HOST)) + ->matchPath( + (empty($testDataPath) || '/' === $testDataPath) + ? '/api/' . $path + : trim(Utils::removeVersionFromUri($testDataPath), '/') . '/' . $path + ); + + if ($header) { + $builder->matchHeader('X-Api-Key', TestConfig::getApiKey()); + } else { + $builder->matchQuery(['apiKey' => TestConfig::getApiKey()]); + } + + return $builder; + } + + /** + * @param \RetailCrm\Api\Interfaces\RequestInterface $request + * + * @return string + * @throws \ReflectionException + */ + public static function encodeForm(RetailCrmRequestInterface $request): string + { + return static::getFormEncoder()->encode($request); + } + + /** + * @param \RetailCrm\Api\Interfaces\RequestInterface $request + * + * @return mixed[] + * @throws \ReflectionException + */ + public static function encodeFormArray(RetailCrmRequestInterface $request): array + { + return static::clearArray(static::getFormEncoder()->encodeArray($request)); + } + + /** + * @param mixed $value + * + * @return string + */ + public static function serialize($value): string + { + return static::getSerializer()->serialize($value, 'json'); + } + + /** + * @param mixed $value + * + * @return mixed[] + */ + public static function serializeArray($value): array + { + return static::getSerializer()->toArray($value); + } + + /** + * Removes all empty fields from arrays, works for nested arrays + * + * @param mixed[] $arr + * + * @return mixed + */ + public static function clearArray(array $arr) + { + if (!is_array($arr)) { + return $arr; + } + + $result = []; + + foreach ($arr as $index => $node) { + $result[$index] = is_array($node) === true ? static::clearArray($node) : trim($node); + + if ( + '' === $result[$index] || + null === $result[$index] || + (is_array($result[$index]) && count($result[$index]) < 1) + ) { + unset($result[$index]); + } + } + + return $result; + } + + /** + * @param string $expectedJson + * @param \RetailCrm\Api\Interfaces\ResponseInterface $response + * @param bool $stripNilValues + * + * @throws \JsonException + */ + protected static function assertModelEqualsToResponse( + string $expectedJson, + RetailCrmResponseInterface $response, + bool $stripNilValues = false + ): void { + $expected = json_decode($expectedJson, true, 512, JSON_THROW_ON_ERROR); + $actual = self::getSerializer()->toArray($response); + + if ($stripNilValues) { + $expected = static::clearArray($expected); + $actual = static::clearArray($actual); + } + + self::assertEquals($expected, $actual); + } + + /** + * @param string $expectedJson + * @param \RetailCrm\Api\Interfaces\ResponseInterface $response + * @param \Closure $callback + * + * @throws \JsonException + */ + protected static function assertModelsCallback( + string $expectedJson, + RetailCrmResponseInterface $response, + Closure $callback + ): void { + $expected = json_decode($expectedJson, true, 512, JSON_THROW_ON_ERROR); + $actual = self::getSerializer()->toArray($response); + + $callback($expected, $actual); + } + + /** + * @param string $text + * + * @return string + */ + private static function addTrailingSlash(string $text): string + { + if ('' === $text) { + return ''; + } + + if ('/' !== $text[strlen($text) - 1]) { + return $text . '/'; + } + + return $text; + } + + /** + * @return \Psr\Http\Message\StreamFactoryInterface + */ + protected static function getStreamFactory(): StreamFactoryInterface + { + if (null === static::$streamFactory) { + static::$streamFactory = Psr17FactoryDiscovery::findStreamFactory(); + } + + return static::$streamFactory; + } + + /** + * @return \Psr\Http\Message\ResponseFactoryInterface + */ + protected static function getResponseFactory(): ResponseFactoryInterface + { + if (null === static::$responseFactory) { + static::$responseFactory = Psr17FactoryDiscovery::findResponseFactory(); + } + + return static::$responseFactory; + } + + /** + * @return \Liip\Serializer\SerializerInterface + */ + protected static function getSerializer(): SerializerInterface + { + if (null === static::$serializer) { + static::$serializer = SerializerFactory::create(); + } + + return static::$serializer; + } + + /** + * @param string $format + * @param string $dateTime + * + * @return \DateTime + */ + protected static function dateTimeFromFormat(string $format, string $dateTime): DateTime + { + $result = DateTime::createFromFormat($format, $dateTime); + + if (!($result instanceof DateTime)) { + throw new RuntimeException(sprintf( + 'Cannot create DateTime with data "%s" with format "%s"', + $format, + $dateTime + )); + } + + return $result; + } + + /** + * @return \RetailCrm\Api\Interfaces\FormEncoderInterface + * @throws \RetailCrm\Api\Exception\BuilderException + */ + private static function getFormEncoder(): FormEncoderInterface + { + if (null === static::$formEncoder) { + static::$formEncoder = (new FormEncoderBuilder())->build(); + } + + return static::$formEncoder; + } +} diff --git a/tests/utils/TestCase/ClientTestCase.php b/tests/utils/TestCase/ClientTestCase.php new file mode 100644 index 0000000..8a669df --- /dev/null +++ b/tests/utils/TestCase/ClientTestCase.php @@ -0,0 +1,111 @@ +getNext()) { + if ($handler instanceof RequestDataHandler) { + return $handler; + } + } + + throw new RuntimeException('RequestDataHandler is not present in the chain.'); + } +} diff --git a/tests/utils/TestConfig.php b/tests/utils/TestConfig.php new file mode 100644 index 0000000..721b049 --- /dev/null +++ b/tests/utils/TestConfig.php @@ -0,0 +1,50 @@ +