diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php
index fb9a9be7..9073564e 100644
--- a/.php-cs-fixer.php
+++ b/.php-cs-fixer.php
@@ -6,6 +6,7 @@
$finder = Finder::create()
->in(__DIR__ . '/src/Infrastructure/Console/Commands/')
+ ->in(__DIR__ . '/src/Services/CRM/Quote/')
->in(__DIR__ . '/src/Services/CRM/Lead/')
->in(__DIR__ . '/src/Services/CRM/Currency/')
->name('*.php')
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0a9bd963..be7371e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,39 @@
### Added
+- Added service `Services\CRM\Quote\Service\Quote` with support methods,
+ see [crm.quote.* methods](https://github.com/bitrix24/b24phpsdk/issues/179):
+ - `fields` returns a list of fields for the quote
+ - `get` returns the settings of the quote by Id
+ - `list` returns a list of quote
+ - `add` creates a new quote
+ - `delete` deletes a quote
+ - `update` modifies the quote
+ - `countByFilter` count quotes by filter
+- Added support for events:
+ - `OnCrmQuoteAdd`
+ - `OnCrmQuoteDelete`
+ - `OnCrmQuoteUpdate`
+ - `OnCrmQuoteUserFieldAdd`
+ - `OnCrmQuoteUserFieldDelete`
+ - `OnCrmQuoteUserFieldSetEnumValues`
+ - `OnCrmQuoteUserFieldUpdate`
+- Added service `Services\CRM\Quote\Service\QuoteUserfield` with support methods:
+ - `add` add userfield to a quote
+ - `get` get userfield to a quote
+ - `list` list userfields
+ - `delete` delete userfield
+ - `update` update userfield
+- Added service `Services\CRM\Quote\Service\QuoteProductRows` with support methods:
+ - `set` Adds products to a quote
+ - `get` Returns the products of a quote
+- Added service `Services\CRM\Quote\Service\QuoteContact` with support methods,
+ - `fields` get fiels for quote contact connection
+ - `setItems` set contacts related with quote
+ - `get` get contacts related to quote
+ - `deleteItems` delete all relations for quote
+ - `add` add contact relation with quote
+ - `delete` delete contact relation with quote
- Added service `CRM\Lead\Service\LeadUserfield` with support methods,
see [add crm.lead.userfield.* methods](https://github.com/bitrix24/b24phpsdk/issues/177):
- `add` add userfield to lead
diff --git a/Makefile b/Makefile
index aa9ae33c..50b45041 100644
--- a/Makefile
+++ b/Makefile
@@ -195,9 +195,9 @@ integration_tests_scope_crm_currency:
integration_tests_deal_recurring:
docker-compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_deal_recurring
-.PHONY: integration_tests_scope_automation
-integration_tests_scope_automation:
- docker-compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_scope_automation
+.PHONY: integration_tests_crm_quote
+integration_tests_crm_quote:
+ docker-compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_crm_quote
# work dev environment
.PHONY: php-dev-server-up
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index fdc9e194..10e8babd 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -11,6 +11,11 @@ parameters:
- tests/Integration/Services/IMOpenLines
- tests/Integration/Services/Main
- tests/Integration/Services/Placement
+ - tests/Integration/Services/CRM/Quote/Service/QuoteTest.php
+ - tests/Integration/Services/CRM/Quote/Service/BatchTest.php
+ - tests/Integration/Services/CRM/Quote/Service/QuoteContactTest.php
+ - tests/Integration/Services/CRM/Quote/Service/QuoteUserfieldTest.php
+ - tests/Integration/Services/CRM/Quote/Service/QuoteProductRowsTest.php
- tests/Integration/Services/CRM/Lead/Service/LeadUserfieldTest.php
- tests/Integration/Services/CRM/Currency
bootstrapFiles:
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 2a703dce..aa8ba6a7 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -58,6 +58,9 @@
./tests/Integration/Services/CRM/Automation/
+
+ ./tests/Integration/Services/CRM/Quote/
+
diff --git a/rector.php b/rector.php
index 5fa6d616..8509a1de 100644
--- a/rector.php
+++ b/rector.php
@@ -36,6 +36,8 @@
__DIR__ . '/tests/Integration/Services/Main',
__DIR__ . '/src/Services/Placement',
__DIR__ . '/tests/Integration/Services/Placement',
+ __DIR__ . '/src/Services/CRM/Quote',
+ __DIR__ . '/tests/Integration/Services/CRM/Quote/Service',
__DIR__ . '/src/Services/CRM/Currency',
__DIR__ . '/tests/Integration/Services/CRM/Currency',
__DIR__ . '/tests/Unit/',
diff --git a/src/Services/CRM/CRMServiceBuilder.php b/src/Services/CRM/CRMServiceBuilder.php
index f2505d40..42f77abd 100644
--- a/src/Services/CRM/CRMServiceBuilder.php
+++ b/src/Services/CRM/CRMServiceBuilder.php
@@ -311,7 +311,7 @@ public function lead(): Lead\Service\Lead
return $this->serviceCache[__METHOD__];
}
-
+
public function leadUserfield(): Lead\Service\LeadUserfield
{
if (!isset($this->serviceCache[__METHOD__])) {
@@ -324,6 +324,56 @@ public function leadUserfield(): Lead\Service\LeadUserfield
return $this->serviceCache[__METHOD__];
}
+
+ public function quote(): Quote\Service\Quote
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new Quote\Service\Quote(
+ new Quote\Service\Batch($this->batch, $this->log),
+ $this->core,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
+ public function quoteContact(): Quote\Service\QuoteContact
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new Quote\Service\QuoteContact(
+ $this->core,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
+ /**
+ * @return Quote\Service\QuoteProductRows
+ */
+ public function quoteProductRows(): Quote\Service\QuoteProductRows
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new Quote\Service\QuoteProductRows($this->core, $this->log);
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
+
+ public function quoteUserfield(): Quote\Service\QuoteUserfield
+ {
+ if (!isset($this->serviceCache[__METHOD__])) {
+ $this->serviceCache[__METHOD__] = new Quote\Service\QuoteUserfield(
+ new UserfieldConstraints(),
+ $this->core,
+ $this->log
+ );
+ }
+
+ return $this->serviceCache[__METHOD__];
+ }
public function activity(): Activity\Service\Activity
{
diff --git a/src/Services/CRM/Quote/Events/CrmQuoteEventsFactory.php b/src/Services/CRM/Quote/Events/CrmQuoteEventsFactory.php
new file mode 100644
index 00000000..97c67361
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/CrmQuoteEventsFactory.php
@@ -0,0 +1,66 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events;
+
+use Bitrix24\SDK\Core\Contracts\Events\EventInterface;
+use Bitrix24\SDK\Core\Contracts\Events\EventsFabricInterface;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteAdd\OnCrmQuoteAdd;
+use Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteDelete\OnCrmQuoteDelete;
+use Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUpdate\OnCrmQuoteUpdate;
+use Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUserFieldAdd\OnCrmQuoteUserFieldAdd;
+use Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUserFieldDelete\OnCrmQuoteUserFieldDelete;
+use Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUserFieldSetEnumValues\OnCrmQuoteUserFieldSetEnumValues;
+use Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUserFieldUpdate\OnCrmQuoteUserFieldUpdate;
+use Symfony\Component\HttpFoundation\Request;
+
+readonly class CrmQuoteEventsFactory implements EventsFabricInterface
+{
+ public function isSupport(string $eventCode): bool
+ {
+ return in_array(strtoupper($eventCode), [
+ OnCrmQuoteAdd::CODE,
+ OnCrmQuoteUpdate::CODE,
+ OnCrmQuoteDelete::CODE,
+ OnCrmQuoteUserFieldAdd::CODE,
+ OnCrmQuoteUserFieldUpdate::CODE,
+ OnCrmQuoteUserFieldDelete::CODE,
+ OnCrmQuoteUserFieldSetEnumValues::CODE,
+ ], true);
+ }
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ public function create(Request $eventRequest): EventInterface
+ {
+ $eventPayload = $eventRequest->request->all();
+ if (!array_key_exists('event', $eventPayload)) {
+ throw new InvalidArgumentException('«event» key not found in event payload');
+ }
+
+ return match ($eventPayload['event']) {
+ OnCrmQuoteAdd::CODE => new OnCrmQuoteAdd($eventRequest),
+ OnCrmQuoteUpdate::CODE => new OnCrmQuoteUpdate($eventRequest),
+ OnCrmQuoteDelete::CODE => new OnCrmQuoteDelete($eventRequest),
+ OnCrmQuoteUserFieldAdd::CODE => new OnCrmQuoteUserFieldAdd($eventRequest),
+ OnCrmQuoteUserFieldUpdate::CODE => new OnCrmQuoteUserFieldUpdate($eventRequest),
+ OnCrmQuoteUserFieldDelete::CODE => new OnCrmQuoteUserFieldDelete($eventRequest),
+ OnCrmQuoteUserFieldSetEnumValues::CODE => new OnCrmQuoteUserFieldSetEnumValues($eventRequest),
+ default => throw new InvalidArgumentException(
+ sprintf('Unexpected event code «%s»', $eventPayload['event'])
+ ),
+ };
+ }
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteAdd/OnCrmQuoteAdd.php b/src/Services/CRM/Quote/Events/OnCrmQuoteAdd/OnCrmQuoteAdd.php
new file mode 100644
index 00000000..baa91311
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteAdd/OnCrmQuoteAdd.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteAdd;
+
+use Bitrix24\SDK\Application\Requests\Events\AbstractEventRequest;
+
+class OnCrmQuoteAdd extends AbstractEventRequest
+{
+ public const CODE = 'ONCRMQUOTEADD';
+
+ public function getPayload(): OnCrmQuoteAddPayload
+ {
+ return new OnCrmQuoteAddPayload($this->eventPayload['data']);
+ }
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteAdd/OnCrmQuoteAddPayload.php b/src/Services/CRM/Quote/Events/OnCrmQuoteAdd/OnCrmQuoteAddPayload.php
new file mode 100644
index 00000000..1a7ce1ad
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteAdd/OnCrmQuoteAddPayload.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteAdd;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+
+/**
+ * @property-read array{
+ * ID: int,
+ * } $FIELDS
+ */
+class OnCrmQuoteAddPayload extends AbstractItem
+{
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteDelete/OnCrmQuoteDelete.php b/src/Services/CRM/Quote/Events/OnCrmQuoteDelete/OnCrmQuoteDelete.php
new file mode 100644
index 00000000..d12294b0
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteDelete/OnCrmQuoteDelete.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteDelete;
+
+use Bitrix24\SDK\Application\Requests\Events\AbstractEventRequest;
+
+class OnCrmQuoteDelete extends AbstractEventRequest
+{
+ public const CODE = 'ONCRMQUOTEDELETE';
+
+ public function getPayload(): OnCrmQuoteDeletePayload
+ {
+ return new OnCrmQuoteDeletePayload($this->eventPayload['data']);
+ }
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteDelete/OnCrmQuoteDeletePayload.php b/src/Services/CRM/Quote/Events/OnCrmQuoteDelete/OnCrmQuoteDeletePayload.php
new file mode 100644
index 00000000..74249ca1
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteDelete/OnCrmQuoteDeletePayload.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteDelete;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+
+/**
+ * @property-read array{
+ * ID: int,
+ * } $FIELDS
+ */
+class OnCrmQuoteDeletePayload extends AbstractItem
+{
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteUpdate/OnCrmQuoteUpdate.php b/src/Services/CRM/Quote/Events/OnCrmQuoteUpdate/OnCrmQuoteUpdate.php
new file mode 100644
index 00000000..aab0d122
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteUpdate/OnCrmQuoteUpdate.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUpdate;
+
+use Bitrix24\SDK\Application\Requests\Events\AbstractEventRequest;
+
+class OnCrmQuoteUpdate extends AbstractEventRequest
+{
+ public const CODE = 'ONCRMQUOTEUPDATE';
+
+ public function getPayload(): OnCrmQuoteUpdatePayload
+ {
+ return new OnCrmQuoteUpdatePayload($this->eventPayload['data']);
+ }
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteUpdate/OnCrmQuoteUpdatePayload.php b/src/Services/CRM/Quote/Events/OnCrmQuoteUpdate/OnCrmQuoteUpdatePayload.php
new file mode 100644
index 00000000..3c34977a
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteUpdate/OnCrmQuoteUpdatePayload.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUpdate;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+
+/**
+ * @property-read array{
+ * ID: int,
+ * RECURRING_DEAL_ID: int,
+ * } $FIELDS
+ */
+class OnCrmQuoteUpdatePayload extends AbstractItem
+{
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldAdd/OnCrmQuoteUserFieldAdd.php b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldAdd/OnCrmQuoteUserFieldAdd.php
new file mode 100644
index 00000000..9c849b12
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldAdd/OnCrmQuoteUserFieldAdd.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUserFieldAdd;
+
+use Bitrix24\SDK\Application\Requests\Events\AbstractEventRequest;
+
+class OnCrmQuoteUserFieldAdd extends AbstractEventRequest
+{
+ public const CODE = 'ONCRMQUOTEUSERFIELDADD';
+
+ public function getPayload(): OnCrmQuoteUserFieldAddPayload
+ {
+ return new OnCrmQuoteUserFieldAddPayload($this->eventPayload['data']);
+ }
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldAdd/OnCrmQuoteUserFieldAddPayload.php b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldAdd/OnCrmQuoteUserFieldAddPayload.php
new file mode 100644
index 00000000..d56bf4fa
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldAdd/OnCrmQuoteUserFieldAddPayload.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUserFieldAdd;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+use Bitrix24\SDK\Services\Telephony\Common\CrmEntityType;
+
+/**
+ * @property-read positive-int $id
+ * @property-read non-empty-string $entityId
+ * @property-read non-empty-string $fieldName
+ */
+class OnCrmQuoteUserFieldAddPayload extends AbstractItem
+{
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldDelete/OnCrmQuoteUserFieldDelete.php b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldDelete/OnCrmQuoteUserFieldDelete.php
new file mode 100644
index 00000000..42280462
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldDelete/OnCrmQuoteUserFieldDelete.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUserFieldDelete;
+
+use Bitrix24\SDK\Application\Requests\Events\AbstractEventRequest;
+
+class OnCrmQuoteUserFieldDelete extends AbstractEventRequest
+{
+ public const CODE = 'ONCRMQUOTEUSERFIELDDELETE';
+
+ public function getPayload(): OnCrmQuoteUserFieldDeletePayload
+ {
+ return new OnCrmQuoteUserFieldDeletePayload($this->eventPayload['data']);
+ }
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldDelete/OnCrmQuoteUserFieldDeletePayload.php b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldDelete/OnCrmQuoteUserFieldDeletePayload.php
new file mode 100644
index 00000000..0650741c
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldDelete/OnCrmQuoteUserFieldDeletePayload.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUserFieldDelete;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+use Bitrix24\SDK\Services\Telephony\Common\CrmEntityType;
+
+/**
+ * @property-read positive-int $id
+ * @property-read non-empty-string $entityId
+ * @property-read non-empty-string $fieldName
+ */
+class OnCrmQuoteUserFieldDeletePayload extends AbstractItem
+{
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldSetEnumValues/OnCrmQuoteUserFieldSetEnumValues.php b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldSetEnumValues/OnCrmQuoteUserFieldSetEnumValues.php
new file mode 100644
index 00000000..a8ec9e53
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldSetEnumValues/OnCrmQuoteUserFieldSetEnumValues.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUserFieldSetEnumValues;
+
+use Bitrix24\SDK\Application\Requests\Events\AbstractEventRequest;
+
+class OnCrmQuoteUserFieldSetEnumValues extends AbstractEventRequest
+{
+ public const CODE = 'ONCRMQUOTEUSERFIELDSETENUMVALUES';
+
+ public function getPayload(): OnCrmQuoteUserFieldSetEnumValuesPayload
+ {
+ return new OnCrmQuoteUserFieldSetEnumValuesPayload($this->eventPayload['data']);
+ }
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldSetEnumValues/OnCrmQuoteUserFieldSetEnumValuesPayload.php b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldSetEnumValues/OnCrmQuoteUserFieldSetEnumValuesPayload.php
new file mode 100644
index 00000000..b366c5ee
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldSetEnumValues/OnCrmQuoteUserFieldSetEnumValuesPayload.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUserFieldSetEnumValues;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+use Bitrix24\SDK\Services\Telephony\Common\CrmEntityType;
+
+/**
+ * @property-read positive-int $id
+ * @property-read non-empty-string $entityId
+ * @property-read non-empty-string $fieldName
+ */
+class OnCrmQuoteUserFieldSetEnumValuesPayload extends AbstractItem
+{
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldUpdate/OnCrmQuoteUserFieldUpdate.php b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldUpdate/OnCrmQuoteUserFieldUpdate.php
new file mode 100644
index 00000000..fcdb5130
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldUpdate/OnCrmQuoteUserFieldUpdate.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUserFieldUpdate;
+
+use Bitrix24\SDK\Application\Requests\Events\AbstractEventRequest;
+
+class OnCrmQuoteUserFieldUpdate extends AbstractEventRequest
+{
+ public const CODE = 'ONCRMQUOTEUSERFIELDUPDATE';
+
+ public function getPayload(): OnCrmQuoteUserFieldUpdatePayload
+ {
+ return new OnCrmQuoteUserFieldUpdatePayload($this->eventPayload['data']);
+ }
+}
diff --git a/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldUpdate/OnCrmQuoteUserFieldUpdatePayload.php b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldUpdate/OnCrmQuoteUserFieldUpdatePayload.php
new file mode 100644
index 00000000..05a71a68
--- /dev/null
+++ b/src/Services/CRM/Quote/Events/OnCrmQuoteUserFieldUpdate/OnCrmQuoteUserFieldUpdatePayload.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Events\OnCrmQuoteUserFieldUpdate;
+
+use Bitrix24\SDK\Core\Result\AbstractItem;
+use Bitrix24\SDK\Services\Telephony\Common\CrmEntityType;
+
+/**
+ * @property-read positive-int $id
+ * @property-read non-empty-string $entityId
+ * @property-read non-empty-string $fieldName
+ */
+class OnCrmQuoteUserFieldUpdatePayload extends AbstractItem
+{
+}
diff --git a/src/Services/CRM/Quote/Result/QuoteContactConnectionItemResult.php b/src/Services/CRM/Quote/Result/QuoteContactConnectionItemResult.php
new file mode 100644
index 00000000..12b20524
--- /dev/null
+++ b/src/Services/CRM/Quote/Result/QuoteContactConnectionItemResult.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Result;
+
+use Bitrix24\SDK\Services\CRM\Common\Result\AbstractCrmItem;
+
+/**
+ * @property-read int $CONTACT_ID
+ * @property-read int $SORT
+ * @property-read bool $IS_PRIMARY
+ */
+class QuoteContactConnectionItemResult extends AbstractCrmItem
+{
+}
diff --git a/src/Services/CRM/Quote/Result/QuoteContactConnectionResult.php b/src/Services/CRM/Quote/Result/QuoteContactConnectionResult.php
new file mode 100644
index 00000000..3af3cd83
--- /dev/null
+++ b/src/Services/CRM/Quote/Result/QuoteContactConnectionResult.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class QuoteContactConnectionResult extends AbstractResult
+{
+ /**
+ * @return QuoteContactConnectionItemResult[]
+ * @throws BaseException
+ */
+ public function getContactConnections(): array
+ {
+ $res = [];
+ foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) {
+ $res[] = new QuoteContactConnectionItemResult($item);
+ }
+
+ return $res;
+ }
+}
diff --git a/src/Services/CRM/Quote/Result/QuoteItemResult.php b/src/Services/CRM/Quote/Result/QuoteItemResult.php
new file mode 100644
index 00000000..9475b36f
--- /dev/null
+++ b/src/Services/CRM/Quote/Result/QuoteItemResult.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Result;
+
+use Bitrix24\SDK\Services\CRM\Common\Result\AbstractCrmItem;
+use Bitrix24\SDK\Services\CRM\Common\Result\SystemFields\Types\Email;
+use Bitrix24\SDK\Services\CRM\Common\Result\SystemFields\Types\InstantMessenger;
+use Bitrix24\SDK\Services\CRM\Common\Result\SystemFields\Types\Phone;
+use Bitrix24\SDK\Services\CRM\Common\Result\SystemFields\Types\Website;
+use Carbon\CarbonImmutable;
+use Money\Currency;
+use Money\Money;
+
+/**
+ * Class QuoteItemResult
+ *
+ * @property-read int $ID
+ * @property-read string $TITLE
+ * @property-read int|null $ASSIGNED_BY_ID
+ * @property-read int|null $LAST_ACTIVITY_BY
+ * @property-read CarbonImmutable|null $LAST_ACTIVITY_TIME
+ * @property-read string|null $LAST_COMMUNICATION_TIME
+ * @property-read CarbonImmutable|null $BEGINDATE
+ * @property-read CarbonImmutable|null $CLOSEDATE
+ * @property-read CarbonImmutable|null $ACTUAL_DATE
+ * @property-read string|null $CLIENT_ADDR
+ * @property-read string|null $CLIENT_CONTACT
+ * @property-read string|null $CLIENT_EMAIL
+ * @property-read string|null $CLIENT_PHONE
+ * @property-read string|null $CLIENT_TITLE
+ * @property-read string|null $CLIENT_TPA_ID
+ * @property-read string|null $CLIENT_TP_ID
+ * @property-read bool|null $CLOSED
+ * @property-read string|null $COMMENTS
+ * @property-read int|null $COMPANY_ID
+ * @property-read int|null $CONTACT_ID
+ * @property-read array|null $CONTACT_IDS
+ * @property-read string|null $CONTENT
+ * @property-read int|null $CREATED_BY_ID
+ * @property-read Currency|null $CURRENCY_ID
+ * @property-read CarbonImmutable|null $DATE_CREATE
+ * @property-read CarbonImmutable|null $DATE_MODIFY
+ * @property-read int|null $DEAL_ID
+ * @property-read int|null $LEAD_ID
+ * @property-read int|null $LOCATION_ID
+ * @property-read int|null $MODIFY_BY_ID
+ * @property-read int|null $MYCOMPANY_ID
+ * @property-read bool|null $OPENED
+ * @property-read Money|null $OPPORTUNITY
+ * @property-read int|null $PERSON_TYPE_ID
+ * @property-read string|null $QUOTE_NUMBER
+ * @property-read string|null $STATUS_ID
+ * @property-read Money|null $TAX_VALUE
+ * @property-read string|null $TERMS
+ * @property-read string|null $UTM_SOURCE
+ * @property-read string|null $UTM_MEDIUM
+ * @property-read string|null $UTM_CAMPAIGN
+ * @property-read string|null $UTM_CONTENT
+ * @property-read string|null $UTM_TERM
+ */
+class QuoteItemResult extends AbstractCrmItem
+{
+ /**
+ *
+ * @return mixed|null
+ * @throws \Bitrix24\SDK\Services\CRM\Userfield\Exceptions\UserfieldNotFoundException
+ */
+ public function getUserfieldByFieldName(string $userfieldName)
+ {
+ return $this->getKeyWithUserfieldByFieldName($userfieldName);
+ }
+}
diff --git a/src/Services/CRM/Quote/Result/QuoteProductRowItemResult.php b/src/Services/CRM/Quote/Result/QuoteProductRowItemResult.php
new file mode 100644
index 00000000..e0eb1897
--- /dev/null
+++ b/src/Services/CRM/Quote/Result/QuoteProductRowItemResult.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Result;
+
+use Money\Money;
+use MoneyPHP\Percentage\Percentage;
+use Bitrix24\SDK\Services\CRM\Common\Result\DiscountType;
+use Carbon\CarbonImmutable;
+use Bitrix24\SDK\Services\CRM\Common\Result\AbstractCrmItem;
+
+/**
+ * @property-read int $ID
+ * @property-read int $OWNER_ID
+ * @property-read string $OWNER_TYPE
+ * @property-read int $PRODUCT_ID
+ * @property-read string $PRODUCT_NAME
+ * @property-read string|null $ORIGINAL_PRODUCT_NAME
+ * @property-read string|null $PRODUCT_DESCRIPTION
+ * @property-read Money $PRICE price with taxes and discounts
+ * @property-read Money $PRICE_EXCLUSIVE without taxes but with discounts
+ * @property-read Money $PRICE_NETTO without taxes and discounts
+ * @property-read Money $PRICE_BRUTTO without discounts but with taxes
+ * @property-read Money $PRICE_ACCOUNT formatted price
+ * @property-read string $QUANTITY
+ * @property-read DiscountType $DISCOUNT_TYPE_ID
+ * @property-read Percentage $DISCOUNT_RATE
+ * @property-read Money $DISCOUNT_SUM
+ * @property-read string $TAX_RATE
+ * @property-read bool $TAX_INCLUDED
+ * @property-read string $CUSTOMIZED
+ * @property-read int $MEASURE_CODE
+ * @property-read string $MEASURE_NAME
+ * @property-read int $SORT
+ * @property-read string|null $XML_ID
+ * @property-read int $TYPE
+ * @property-read int|null $STORE_ID
+ * @property-read int|null $RESERVE_ID
+ * @property-read CarbonImmutable|null $DATE_RESERVE_END
+ * @property-read int|null $RESERVE_QUANTITY
+ */
+class QuoteProductRowItemResult extends AbstractCrmItem
+{
+}
diff --git a/src/Services/CRM/Quote/Result/QuoteProductRowItemsResult.php b/src/Services/CRM/Quote/Result/QuoteProductRowItemsResult.php
new file mode 100644
index 00000000..b7412d5a
--- /dev/null
+++ b/src/Services/CRM/Quote/Result/QuoteProductRowItemsResult.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Response\Response;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+use Money\Currency;
+
+/**
+ * Class QuoteProductRowItemsResult
+ *
+ * @package Bitrix24\SDK\Services\CRM\Quote\Result
+ */
+class QuoteProductRowItemsResult extends AbstractResult
+{
+ public function __construct(Response $coreResponse, private readonly Currency $currency)
+ {
+ parent::__construct($coreResponse);
+ }
+
+ /**
+ * @return QuoteProductRowItemResult[]
+ * @throws BaseException
+ */
+ public function getProductRows(): array
+ {
+ $res = [];
+ if (!empty($this->getCoreResponse()->getResponseData()->getResult()['result']['rows'])) {
+ foreach ($this->getCoreResponse()->getResponseData()->getResult()['result']['rows'] as $productRow) {
+ $res[] = new QuoteProductRowItemResult($productRow, $this->currency);
+ }
+ } else {
+ foreach ($this->getCoreResponse()->getResponseData()->getResult() as $productRow) {
+ $res[] = new QuoteProductRowItemResult($productRow, $this->currency);
+ }
+ }
+
+ return $res;
+ }
+}
diff --git a/src/Services/CRM/Quote/Result/QuoteResult.php b/src/Services/CRM/Quote/Result/QuoteResult.php
new file mode 100644
index 00000000..1cf4b9af
--- /dev/null
+++ b/src/Services/CRM/Quote/Result/QuoteResult.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class QuoteResult
+ *
+ * @package Bitrix24\SDK\Services\CRM\Quote\Result
+ */
+class QuoteResult extends AbstractResult
+{
+ /**
+ * @throws \Bitrix24\SDK\Core\Exceptions\BaseException
+ */
+ public function quote(): QuoteItemResult
+ {
+ return new QuoteItemResult($this->getCoreResponse()->getResponseData()->getResult());
+ }
+}
diff --git a/src/Services/CRM/Quote/Result/QuoteUserfieldItemResult.php b/src/Services/CRM/Quote/Result/QuoteUserfieldItemResult.php
new file mode 100644
index 00000000..9b8f0301
--- /dev/null
+++ b/src/Services/CRM/Quote/Result/QuoteUserfieldItemResult.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Result;
+
+use Bitrix24\SDK\Services\CRM\Userfield\Result\AbstractUserfieldItemResult;
+
+class QuoteUserfieldItemResult extends AbstractUserfieldItemResult
+{
+}
diff --git a/src/Services/CRM/Quote/Result/QuoteUserfieldResult.php b/src/Services/CRM/Quote/Result/QuoteUserfieldResult.php
new file mode 100644
index 00000000..b4cbe9f9
--- /dev/null
+++ b/src/Services/CRM/Quote/Result/QuoteUserfieldResult.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Result;
+
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class QuoteUserfieldResult extends AbstractResult
+{
+ /**
+ * @throws \Bitrix24\SDK\Core\Exceptions\BaseException
+ */
+ public function userfieldItem(): QuoteUserfieldItemResult
+ {
+ return new QuoteUserfieldItemResult($this->getCoreResponse()->getResponseData()->getResult());
+ }
+}
diff --git a/src/Services/CRM/Quote/Result/QuoteUserfieldsResult.php b/src/Services/CRM/Quote/Result/QuoteUserfieldsResult.php
new file mode 100644
index 00000000..414091f0
--- /dev/null
+++ b/src/Services/CRM/Quote/Result/QuoteUserfieldsResult.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+class QuoteUserfieldsResult extends AbstractResult
+{
+ /**
+ * @return \Bitrix24\SDK\Services\CRM\Quote\Result\QuoteUserfieldItemResult[]
+ * @throws BaseException
+ */
+ public function getUserfields(): array
+ {
+ $res = [];
+ foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) {
+ $res[] = new QuoteUserfieldItemResult($item);
+ }
+
+ return $res;
+ }
+}
diff --git a/src/Services/CRM/Quote/Result/QuotesResult.php b/src/Services/CRM/Quote/Result/QuotesResult.php
new file mode 100644
index 00000000..f676dade
--- /dev/null
+++ b/src/Services/CRM/Quote/Result/QuotesResult.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Result;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AbstractResult;
+
+/**
+ * Class QuotesResult
+ *
+ * @package Bitrix24\SDK\Services\CRM\Quote\Result
+ */
+class QuotesResult extends AbstractResult
+{
+ /**
+ * @return QuoteItemResult[]
+ * @throws BaseException
+ */
+ public function getQuotes(): array
+ {
+ $items = [];
+ foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) {
+ $items[] = new QuoteItemResult($item);
+ }
+
+ return $items;
+ }
+}
diff --git a/src/Services/CRM/Quote/Service/Batch.php b/src/Services/CRM/Quote/Service/Batch.php
new file mode 100644
index 00000000..59496c2a
--- /dev/null
+++ b/src/Services/CRM/Quote/Service/Batch.php
@@ -0,0 +1,242 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Service;
+
+use Bitrix24\SDK\Attributes\ApiBatchMethodMetadata;
+use Bitrix24\SDK\Attributes\ApiBatchServiceMetadata;
+use Bitrix24\SDK\Core\Contracts\BatchOperationsInterface;
+use Bitrix24\SDK\Core\Credentials\Scope;
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Result\AddedItemBatchResult;
+use Bitrix24\SDK\Core\Result\DeletedItemBatchResult;
+use Bitrix24\SDK\Core\Result\UpdatedItemBatchResult;
+use Bitrix24\SDK\Services\CRM\Quote\Result\QuoteItemResult;
+use Generator;
+use Psr\Log\LoggerInterface;
+
+#[ApiBatchServiceMetadata(new Scope(['crm']))]
+class Batch
+{
+ /**
+ * Batch constructor.
+ */
+ public function __construct(protected BatchOperationsInterface $batch, protected LoggerInterface $log)
+ {
+ }
+
+ /**
+ * Batch list method for quotes
+ *
+ * @param array{
+ * ID?: int,
+ * ASSIGNED_BY_ID?: int,
+ * BEGINDATA?: string,
+ * CLIENT_ADDR?: string,
+ * CLOSED?: bool,
+ * CLOSEDATA?: string,
+ * COMMENTS?: string,
+ * COMPANY_ID?: int,
+ * CONTACT_ID?: int,
+ * CONTACT_IDS?: int[],
+ * CONTENT?: string,
+ * CREATED_BY_ID?: int,
+ * CURRENCY_ID?: string,
+ * DATE_CREATE?: string,
+ * DATE_MODIFY?: string,
+ * DEAL_ID?: int,
+ * LEAD_ID?: int,
+ * LOCATION_ID?: int,
+ * MODIFY_BY_ID?: int,
+ * MYCOMPANY_ID?: int,
+ * OPENED?: bool,
+ * OPPORTUNITY?: string,
+ * PERSON_TYPE_ID?: int,
+ * QUOTE_NUMBER?: string,
+ * STATUS_ID?: string,
+ * TAX_VALUE?: string,
+ * TERMS?: string,
+ * TITLE?: string,
+ * UTM_CAMPAIGN?: string,
+ * UTM_CONTENT?: string,
+ * UTM_MEDIUM?: string,
+ * UTM_SOURCE?: string,
+ * UTM_TERM?: string,
+ * } $order
+ *
+ * @param array{
+ * ID?: int,
+ * ASSIGNED_BY_ID?: int,
+ * BEGINDATA?: string,
+ * CLIENT_ADDR?: string,
+ * CLOSED?: bool,
+ * CLOSEDATA?: string,
+ * COMMENTS?: string,
+ * COMPANY_ID?: int,
+ * CONTACT_ID?: int,
+ * CONTACT_IDS?: int[],
+ * CONTENT?: string,
+ * CREATED_BY_ID?: int,
+ * CURRENCY_ID?: string,
+ * DATE_CREATE?: string,
+ * DATE_MODIFY?: string,
+ * DEAL_ID?: int,
+ * LEAD_ID?: int,
+ * LOCATION_ID?: int,
+ * MODIFY_BY_ID?: int,
+ * MYCOMPANY_ID?: int,
+ * OPENED?: bool,
+ * OPPORTUNITY?: string,
+ * PERSON_TYPE_ID?: int,
+ * QUOTE_NUMBER?: string,
+ * STATUS_ID?: string,
+ * TAX_VALUE?: string,
+ * TERMS?: string,
+ * TITLE?: string,
+ * UTM_CAMPAIGN?: string,
+ * UTM_CONTENT?: string,
+ * UTM_MEDIUM?: string,
+ * UTM_SOURCE?: string,
+ * UTM_TERM?: string,
+ * } $filter
+ * @param array $select = ['ID','ASSIGNED_BY_ID','BEGINDATA','CLIENT_ADDR','CLOSED','CLOSEDATA','COMMENTS','COMPANY_ID','CONTACT_ID','CONTACT_IDS','CONTENT','CREATED_BY_ID','CURRENCY_ID','DATE_CREATE','DATE_MODIFY','DEAL_ID','LEAD_ID','LOCATION_ID','MODIFY_BY_ID','MYCOMPANY_ID','OPENED','OPPORTUNITY','PERSON_TYPE_ID','QUOTE_NUMBER','STATUS_ID','TAX_VALUE','TERMS','TITLE','UTM_CAMPAIGN','UTM_CONTENT','UTM_MEDIUM','UTM_SOURCE','UTM_TERM']
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'crm.quote.list',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-list.html',
+ 'Batch list method for quotes'
+ )]
+ public function list(array $order, array $filter, array $select, ?int $limit = null): Generator
+ {
+ $this->log->debug(
+ 'batchList',
+ [
+ 'order' => $order,
+ 'filter' => $filter,
+ 'select' => $select,
+ 'limit' => $limit,
+ ]
+ );
+ foreach ($this->batch->getTraversableList('crm.quote.list', $order, $filter, $select, $limit) as $key => $value) {
+ yield $key => new QuoteItemResult($value);
+ }
+ }
+
+ /**
+ * Batch adding quotes
+ *
+ * @param array $quotes
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'crm.quote.add',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-add.html',
+ 'Batch adding quotes'
+ )]
+ public function add(array $quotes): Generator
+ {
+ $items = [];
+ foreach ($quotes as $quote) {
+ $items[] = [
+ 'fields' => $quote,
+ ];
+ }
+
+ foreach ($this->batch->addEntityItems('crm.quote.add', $items) as $key => $item) {
+ yield $key => new AddedItemBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch update quotes
+ *
+ * Update elements in array with structure
+ * element_id => [ // quote id
+ * 'fields' => [] // quote fields to update
+ * ]
+ *
+ * @param array $entityItems
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'crm.quote.update',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-update.html',
+ 'Update in batch mode a list of quotes'
+ )]
+ public function update(array $entityItems): Generator
+ {
+ foreach ($this->batch->updateEntityItems('crm.quote.update', $entityItems) as $key => $item) {
+ yield $key => new UpdatedItemBatchResult($item);
+ }
+ }
+
+ /**
+ * Batch delete quotes
+ *
+ * @param int[] $quoteId
+ *
+ * @return Generator
+ * @throws BaseException
+ */
+ #[ApiBatchMethodMetadata(
+ 'crm.quote.delete',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-delete.html',
+ 'Batch delete quotes'
+ )]
+ public function delete(array $quoteId): Generator
+ {
+ foreach ($this->batch->deleteEntityItems('crm.quote.delete', $quoteId) as $key => $item) {
+ yield $key => new DeletedItemBatchResult($item);
+ }
+ }
+}
diff --git a/src/Services/CRM/Quote/Service/Quote.php b/src/Services/CRM/Quote/Service/Quote.php
new file mode 100644
index 00000000..db47bd18
--- /dev/null
+++ b/src/Services/CRM/Quote/Service/Quote.php
@@ -0,0 +1,317 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Service;
+
+use Bitrix24\SDK\Attributes\ApiEndpointMetadata;
+use Bitrix24\SDK\Attributes\ApiServiceMetadata;
+use Bitrix24\SDK\Core\Contracts\CoreInterface;
+use Bitrix24\SDK\Core\Credentials\Scope;
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Core\Result\AddedItemResult;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+use Bitrix24\SDK\Core\Result\FieldsResult;
+use Bitrix24\SDK\Core\Result\UpdatedItemResult;
+use Bitrix24\SDK\Services\AbstractService;
+use Bitrix24\SDK\Services\CRM\Quote\Result\QuoteResult;
+use Bitrix24\SDK\Services\CRM\Quote\Result\QuotesResult;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['crm']))]
+class Quote extends AbstractService
+{
+ /**
+ * Quote constructor.
+ */
+ public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * add new quote
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-add.html
+ *
+ * @param array{
+ * ID?: int,
+ * ASSIGNED_BY_ID?: int,
+ * LAST_ACTIVITY_BY?: int,
+ * LAST_ACTIVITY_TIME?: string,
+ * LAST_COMMUNICATION_TIME?: string,
+ * BEGINDATE?: string,
+ * CLOSEDATE?: string,
+ * ACTUAL_DATE?: string,
+ * CLIENT_ADDR?: string,
+ * CLIENT_CONTACT?: string,
+ * CLIENT_EMAIL?: string,
+ * CLIENT_PHONE?: string,
+ * CLIENT_TITLE?: string,
+ * CLIENT_TPA_ID?: string,
+ * CLIENT_TP_ID?: string,
+ * CLOSED?: bool,
+ * COMMENTS?: string,
+ * COMPANY_ID?: int,
+ * CONTACT_ID?: int,
+ * CONTACT_IDS?: int[],
+ * CONTENT?: string,
+ * CREATED_BY_ID?: int,
+ * CURRENCY_ID?: string,
+ * DATE_CREATE?: string,
+ * DATE_MODIFY?: string,
+ * DEAL_ID?: int,
+ * LEAD_ID?: int,
+ * LOCATION_ID?: int,
+ * MODIFY_BY_ID?: int,
+ * MYCOMPANY_ID?: int,
+ * OPENED?: bool,
+ * OPPORTUNITY?: string,
+ * PERSON_TYPE_ID?: int,
+ * QUOTE_NUMBER?: string,
+ * STATUS_ID?: string,
+ * TAX_VALUE?: string,
+ * TERMS?: string,
+ * TITLE?: string,
+ * UTM_CAMPAIGN?: string,
+ * UTM_CONTENT?: string,
+ * UTM_MEDIUM?: string,
+ * UTM_SOURCE?: string,
+ * UTM_TERM?: string,
+ * } $fields
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.add',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-add.html',
+ 'Method adds new quote'
+ )]
+ public function add(array $fields): AddedItemResult
+ {
+ return new AddedItemResult(
+ $this->core->call(
+ 'crm.quote.add',
+ [
+ 'fields' => $fields
+ ]
+ )
+ );
+ }
+
+ /**
+ * Deletes the specified quote and all the associated objects.
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-delete.html
+ *
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.delete',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-delete.html',
+ 'Deletes the specified quote and all the associated objects.'
+ )]
+ public function delete(int $id): DeletedItemResult
+ {
+ return new DeletedItemResult(
+ $this->core->call(
+ 'crm.quote.delete',
+ [
+ 'id' => $id,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Returns the description of the quote fields, including user fields.
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-fields.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.fields',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-fields.html',
+ 'Returns the description of the quote fields, including user fields.'
+ )]
+ public function fields(): FieldsResult
+ {
+ return new FieldsResult($this->core->call('crm.quote.fields'));
+ }
+
+ /**
+ * Returns a quote by the quote ID.
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-get.html
+ *
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.get',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-get.html',
+ 'Returns a quote by the quote ID.'
+ )]
+ public function get(int $id): QuoteResult
+ {
+ return new QuoteResult($this->core->call('crm.quote.get', ['id' => $id]));
+ }
+
+ /**
+ * Get list of quote items.
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-list.html
+ *
+ * @param array $order - order of quote items
+ * @param array $filter - filter array
+ * @param array $select = ['ID','ASSIGNED_BY_ID','BEGINDATA','CLIENT_ADDR','CLOSED','CLOSEDATA','COMMENTS','COMPANY_ID','CONTACT_ID','CONTACT_IDS','CONTENT','CREATED_BY_ID','CURRENCY_ID','DATE_CREATE','DATE_MODIFY','DEAL_ID','LEAD_ID','LOCATION_ID','MODIFY_BY_ID','MYCOMPANY_ID','OPENED','OPPORTUNITY','PERSON_TYPE_ID','QUOTE_NUMBER','STATUS_ID','TAX_VALUE','TERMS','TITLE','UTM_CAMPAIGN','UTM_CONTENT','UTM_MEDIUM','UTM_SOURCE','UTM_TERM']
+ * @param integer $startItem - entity number to start from (usually returned in 'next' field of previous 'crm.quote.list' API call)
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.list',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-list.html',
+ 'Get list of quote items.'
+ )]
+ public function list(array $order, array $filter, array $select, int $startItem = 0): QuotesResult
+ {
+ return new QuotesResult(
+ $this->core->call(
+ 'crm.quote.list',
+ [
+ 'order' => $order,
+ 'filter' => $filter,
+ 'select' => $select,
+ 'start' => $startItem,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Updates the specified (existing) quote.
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-update.html
+ *
+ * @param array{
+ * ID?: int,
+ * ASSIGNED_BY_ID?: int,
+ * BEGINDATA?: string,
+ * CLIENT_ADDR?: string,
+ * CLOSED?: bool,
+ * CLOSEDATA?: string,
+ * COMMENTS?: string,
+ * COMPANY_ID?: int,
+ * CONTACT_ID?: int,
+ * CONTACT_IDS?: int[],
+ * CONTENT?: string,
+ * CREATED_BY_ID?: int,
+ * CURRENCY_ID?: string,
+ * DATE_CREATE?: string,
+ * DATE_MODIFY?: string,
+ * DEAL_ID?: int,
+ * LEAD_ID?: int,
+ * LOCATION_ID?: int,
+ * MODIFY_BY_ID?: int,
+ * MYCOMPANY_ID?: int,
+ * OPENED?: bool,
+ * OPPORTUNITY?: string,
+ * PERSON_TYPE_ID?: int,
+ * QUOTE_NUMBER?: string,
+ * STATUS_ID?: string,
+ * TAX_VALUE?: string,
+ * TERMS?: string,
+ * TITLE?: string,
+ * UTM_CAMPAIGN?: string,
+ * UTM_CONTENT?: string,
+ * UTM_MEDIUM?: string,
+ * UTM_SOURCE?: string,
+ * UTM_TERM?: string,
+ * } $fields
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.update',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-update.html',
+ 'Updates the specified (existing) quote.'
+ )]
+ public function update(int $id, array $fields): UpdatedItemResult
+ {
+ return new UpdatedItemResult(
+ $this->core->call(
+ 'crm.quote.update',
+ [
+ 'id' => $id,
+ 'fields' => $fields
+ ]
+ )
+ );
+ }
+
+ /**
+ * Count quotes by filter
+ *
+ * @param array{
+ * ID?: int,
+ * ASSIGNED_BY_ID?: int,
+ * BEGINDATA?: string,
+ * CLIENT_ADDR?: string,
+ * CLOSED?: bool,
+ * CLOSEDATA?: string,
+ * COMMENTS?: string,
+ * COMPANY_ID?: int,
+ * CONTACT_ID?: int,
+ * CONTACT_IDS?: int[],
+ * CONTENT?: string,
+ * CREATED_BY_ID?: int,
+ * CURRENCY_ID?: string,
+ * DATE_CREATE?: string,
+ * DATE_MODIFY?: string,
+ * DEAL_ID?: int,
+ * LEAD_ID?: int,
+ * LOCATION_ID?: int,
+ * MODIFY_BY_ID?: int,
+ * MYCOMPANY_ID?: int,
+ * OPENED?: bool,
+ * OPPORTUNITY?: string,
+ * PERSON_TYPE_ID?: int,
+ * QUOTE_NUMBER?: string,
+ * STATUS_ID?: string,
+ * TAX_VALUE?: string,
+ * TERMS?: string,
+ * TITLE?: string,
+ * UTM_CAMPAIGN?: string,
+ * UTM_CONTENT?: string,
+ * UTM_MEDIUM?: string,
+ * UTM_SOURCE?: string,
+ * UTM_TERM?: string,
+ * } $filter
+ *
+ * @throws \Bitrix24\SDK\Core\Exceptions\BaseException
+ * @throws \Bitrix24\SDK\Core\Exceptions\TransportException
+ */
+ public function countByFilter(array $filter = []): int
+ {
+ return $this->list([], $filter, ['ID'], 1)->getCoreResponse()->getResponseData()->getPagination()->getTotal();
+ }
+}
diff --git a/src/Services/CRM/Quote/Service/QuoteContact.php b/src/Services/CRM/Quote/Service/QuoteContact.php
new file mode 100644
index 00000000..910504e4
--- /dev/null
+++ b/src/Services/CRM/Quote/Service/QuoteContact.php
@@ -0,0 +1,174 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Service;
+
+use Bitrix24\SDK\Attributes\ApiServiceMetadata;
+use Bitrix24\SDK\Core\Contracts\CoreInterface;
+use Bitrix24\SDK\Core\Credentials\Scope;
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+use Bitrix24\SDK\Core\Result\FieldsResult;
+use Bitrix24\SDK\Core\Result\UpdatedItemResult;
+use Bitrix24\SDK\Services\AbstractService;
+use Bitrix24\SDK\Services\CRM\Common\ContactConnection;
+use Bitrix24\SDK\Services\CRM\Quote\Result\QuoteContactConnectionResult;
+use Psr\Log\LoggerInterface;
+use Bitrix24\SDK\Attributes\ApiEndpointMetadata;
+
+#[ApiServiceMetadata(new Scope(['crm']))]
+class QuoteContact extends AbstractService
+{
+ /**
+ * Get Field Descriptions for Estimate-Contact Connection
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/index.html
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.contact.fields',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/index.html',
+ 'Get Field Descriptions for Estimate-Contact Connection'
+ )]
+ public function fields(): FieldsResult
+ {
+ return new FieldsResult($this->core->call('crm.quote.contact.fields'));
+ }
+
+ /**
+ * Set a set of contacts associated with the specified estimate
+ *
+ * @param non-negative-int $quoteId
+ * @param ContactConnection[] $contactConnections
+ * @throws InvalidArgumentException
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/index.html
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.contact.items.set',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/index.html',
+ 'Set a set of contacts associated with the specified estimate crm.quote.contact.items.set'
+ )]
+ public function setItems(int $quoteId, array $contactConnections): UpdatedItemResult
+ {
+ $items = [];
+ foreach ($contactConnections as $contactConnection) {
+ if (!$contactConnection instanceof ContactConnection) {
+ throw new InvalidArgumentException(
+ sprintf('array item «%s» must be «%s» type', gettype($contactConnection), ContactConnection::class)
+ );
+ }
+
+ $items[] = [
+ 'CONTACT_ID' => $contactConnection->contactId,
+ 'SORT' => $contactConnection->sort,
+ 'IS_PRIMARY' => $contactConnection->isPrimary ? 'Y' : 'N'
+ ];
+ }
+
+ if ($items === []) {
+ throw new InvalidArgumentException('empty contact connections array');
+ }
+
+ return new UpdatedItemResult(
+ $this->core->call('crm.quote.contact.items.set', [
+ 'id' => $quoteId,
+ 'items' => $items
+ ])
+ );
+ }
+
+ /**
+ * Get a set of contacts associated with the specified estimate
+ *
+ * @param non-negative-int $quoteId
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/index.html
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.contact.items.get',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/index.html',
+ 'Get a set of contacts associated with the specified estimate crm.quote.contact.items.get'
+ )]
+ public function get(int $quoteId): QuoteContactConnectionResult
+ {
+ return new QuoteContactConnectionResult($this->core->call('crm.quote.contact.items.get', [
+ 'id' => $quoteId
+ ]));
+ }
+
+ /**
+ * Get a set of contacts associated with the specified estimates
+ *
+ * @param non-negative-int $quoteId
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/index.html
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.contact.items.delete',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/index.html',
+ 'Clear the set of contacts associated with the specified estimate'
+ )]
+ public function deleteItems(int $quoteId): DeletedItemResult
+ {
+ return new DeletedItemResult($this->core->call('crm.quote.contact.items.delete', [
+ 'id' => $quoteId
+ ]));
+ }
+
+ /**
+ * Add Contact to the Specified Estimate
+ *
+ * @param non-negative-int $quoteId
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/index.html
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.contact.add',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/index.html',
+ 'Add Contact to the Specified Estimate'
+ )]
+ public function add(int $quoteId, ContactConnection $contactConnection): UpdatedItemResult
+ {
+ return new UpdatedItemResult($this->core->call('crm.quote.contact.add', [
+ 'id' => $quoteId,
+ 'fields' => [
+ 'CONTACT_ID' => $contactConnection->contactId,
+ 'SORT' => $contactConnection->sort,
+ 'IS_PRIMARY' => $contactConnection->isPrimary ? 'Y' : 'N'
+ ]
+ ]));
+ }
+
+ /**
+ * Delete Contact from Specified Estimate
+ *
+ * @param non-negative-int $quoteId
+ * @param non-negative-int $contactId
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/index.html
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.contact.delete',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/index.html',
+ 'Delete Contact from Specified Estimate'
+ )]
+ public function delete(int $quoteId, int $contactId): DeletedItemResult
+ {
+ return new DeletedItemResult($this->core->call('crm.quote.contact.delete', [
+ 'id' => $quoteId,
+ 'fields' => [
+ 'CONTACT_ID' => $contactId
+ ]
+ ]));
+ }
+}
diff --git a/src/Services/CRM/Quote/Service/QuoteProductRows.php b/src/Services/CRM/Quote/Service/QuoteProductRows.php
new file mode 100644
index 00000000..3a224552
--- /dev/null
+++ b/src/Services/CRM/Quote/Service/QuoteProductRows.php
@@ -0,0 +1,117 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Service;
+
+use Bitrix24\SDK\Attributes\ApiEndpointMetadata;
+use Bitrix24\SDK\Attributes\ApiServiceMetadata;
+use Bitrix24\SDK\Core\Credentials\Scope;
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Core\Result\UpdatedItemResult;
+use Bitrix24\SDK\Services\AbstractService;
+use Bitrix24\SDK\Services\CRM\Quote\Result\QuoteProductRowItemsResult;
+use Money\Currency;
+
+#[ApiServiceMetadata(new Scope(['crm']))]
+class QuoteProductRows extends AbstractService
+{
+ /**
+ * Returns products inside the specified quote.
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-product-rows-get.html
+ *
+ * @param Currency|null $currency
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.productrows.get',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-product-rows-get.html',
+ 'Returns products inside the specified quote.'
+ )]
+ public function get(int $quoteId, Currency $currency = null): QuoteProductRowItemsResult
+ {
+ if (!$currency instanceof \Money\Currency) {
+ $res = $this->core->call('batch', [
+ 'halt' => 0,
+ 'cmd' => [
+ 'quote' => sprintf('crm.quote.get?ID=%s', $quoteId),
+ 'rows' => sprintf('crm.quote.productrows.get?ID=%s', $quoteId)
+ ],
+ ]);
+ $data = $res->getResponseData()->getResult();
+ $currency = new Currency($data['result']['quote']['CURRENCY_ID']);
+ return new QuoteProductRowItemsResult($res, $currency);
+ }
+
+ return new QuoteProductRowItemsResult(
+ $this->core->call(
+ 'crm.quote.productrows.get',
+ [
+ 'id' => $quoteId,
+ ]
+ ),
+ $currency
+ );
+ }
+
+
+ /**
+ * Creates or updates product entries inside the specified quote.
+ *
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-product-rows-set.html
+ *
+ * @param array $productRows
+ *
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.productrows.set',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/crm-quote-product-rows-set.html',
+ 'Creates or updates product entries inside the specified quote.'
+ )]
+ public function set(int $quoteId, array $productRows): UpdatedItemResult
+ {
+ return new UpdatedItemResult(
+ $this->core->call(
+ 'crm.quote.productrows.set',
+ [
+ 'id' => $quoteId,
+ 'rows' => $productRows,
+ ]
+ )
+ );
+ }
+}
diff --git a/src/Services/CRM/Quote/Service/QuoteUserfield.php b/src/Services/CRM/Quote/Service/QuoteUserfield.php
new file mode 100644
index 00000000..1de5a51c
--- /dev/null
+++ b/src/Services/CRM/Quote/Service/QuoteUserfield.php
@@ -0,0 +1,236 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Services\CRM\Quote\Service;
+
+use Bitrix24\SDK\Attributes\ApiEndpointMetadata;
+use Bitrix24\SDK\Attributes\ApiServiceMetadata;
+use Bitrix24\SDK\Core\Contracts\CoreInterface;
+use Bitrix24\SDK\Core\Credentials\Scope;
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Core\Result\AddedItemResult;
+use Bitrix24\SDK\Core\Result\DeletedItemResult;
+use Bitrix24\SDK\Core\Result\UpdatedItemResult;
+use Bitrix24\SDK\Services\AbstractService;
+use Bitrix24\SDK\Services\CRM\Quote\Result\QuoteUserfieldResult;
+use Bitrix24\SDK\Services\CRM\Quote\Result\QuoteUserfieldsResult;
+use Bitrix24\SDK\Services\CRM\Userfield\Exceptions\UserfieldNameIsTooLongException;
+use Bitrix24\SDK\Services\CRM\Userfield\Service\UserfieldConstraints;
+use Psr\Log\LoggerInterface;
+
+#[ApiServiceMetadata(new Scope(['crm']))]
+class QuoteUserfield extends AbstractService
+{
+ public function __construct(private readonly UserfieldConstraints $userfieldConstraints, CoreInterface $core, LoggerInterface $logger)
+ {
+ parent::__construct($core, $logger);
+ }
+
+ /**
+ * Returns list of user quote fields by filter.
+ *
+ * @param array{
+ * ID?: string,
+ * ENTITY_ID?: string,
+ * FIELD_NAME?: string,
+ * USER_TYPE_ID?: string,
+ * XML_ID?: string,
+ * SORT?: string,
+ * MULTIPLE?: string,
+ * MANDATORY?: string,
+ * SHOW_FILTER?: string,
+ * SHOW_IN_LIST?: string,
+ * EDIT_IN_LIST?: string,
+ * IS_SEARCHABLE?: string,
+ * EDIT_FORM_LABEL?: string,
+ * LIST_COLUMN_LABEL?: string,
+ * LIST_FILTER_LABEL?: string,
+ * ERROR_MESSAGE?: string,
+ * HELP_MESSAGE?: string,
+ * LIST?: string,
+ * SETTINGS?: string,
+ * } $order
+ * @param array{
+ * ID?: string,
+ * ENTITY_ID?: string,
+ * FIELD_NAME?: string,
+ * USER_TYPE_ID?: string,
+ * XML_ID?: string,
+ * SORT?: string,
+ * MULTIPLE?: string,
+ * MANDATORY?: string,
+ * SHOW_FILTER?: string,
+ * SHOW_IN_LIST?: string,
+ * EDIT_IN_LIST?: string,
+ * IS_SEARCHABLE?: string,
+ * EDIT_FORM_LABEL?: string,
+ * LIST_COLUMN_LABEL?: string,
+ * LIST_FILTER_LABEL?: string,
+ * ERROR_MESSAGE?: string,
+ * HELP_MESSAGE?: string,
+ * LIST?: string,
+ * SETTINGS?: string,
+ * } $filter
+ *
+ * @throws BaseException
+ * @throws TransportException
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/user-field/crm-quote-user-field-list.html
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.userfield.list',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/user-field/crm-quote-user-field-list.html',
+ 'Returns list of user quote fields by filter.'
+ )]
+ public function list(array $order, array $filter): QuoteUserfieldsResult
+ {
+ return new QuoteUserfieldsResult(
+ $this->core->call(
+ 'crm.quote.userfield.list',
+ [
+ 'order' => $order,
+ 'filter' => $filter,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Created new user field for quotes.
+ *
+ * System limitation for field name - 20 characters.
+ * Prefix UF_CRM_is always added to the user field name.
+ * As a result, the actual name length - 13 characters.
+ *
+ * @param array{
+ * FIELD_NAME?: string,
+ * USER_TYPE_ID?: string,
+ * XML_ID?: string,
+ * SORT?: string,
+ * MULTIPLE?: string,
+ * MANDATORY?: string,
+ * SHOW_FILTER?: string,
+ * SHOW_IN_LIST?: string,
+ * EDIT_IN_LIST?: string,
+ * IS_SEARCHABLE?: string,
+ * EDIT_FORM_LABEL?: string|array,
+ * LIST_COLUMN_LABEL?: string|array,
+ * LIST_FILTER_LABEL?: string|array,
+ * ERROR_MESSAGE?: string,
+ * HELP_MESSAGE?: string,
+ * LIST?: string,
+ * SETTINGS?: array,
+ * } $userfieldItemFields
+ *
+ * @throws BaseException
+ * @throws TransportException
+ * @throws UserfieldNameIsTooLongException
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/user-field/crm-quote-user-field-add.html
+ *
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.userfield.add',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/user-field/crm-quote-user-field-add.html',
+ 'Created new user field for quotes.'
+ )]
+ public function add(array $userfieldItemFields): AddedItemResult
+ {
+ $this->userfieldConstraints->validName($userfieldItemFields['FIELD_NAME']);
+
+ return new AddedItemResult(
+ $this->core->call(
+ 'crm.quote.userfield.add',
+ [
+ 'fields' => $userfieldItemFields,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Deleted userfield for quotes
+ *
+ *
+ * @throws BaseException
+ * @throws TransportException
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/user-field/crm-quote-user-field-delete.html
+ *
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.userfield.delete',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/user-field/crm-quote-user-field-delete.html',
+ 'Deleted userfield for quotes'
+ )]
+ public function delete(int $userfieldId): DeletedItemResult
+ {
+ return new DeletedItemResult(
+ $this->core->call(
+ 'crm.quote.userfield.delete',
+ [
+ 'id' => $userfieldId,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Returns a userfield for quote by ID.
+ *
+ *
+ * @throws BaseException
+ * @throws TransportException
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/user-field/crm-quote-user-field-get.html
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.userfield.get',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/user-field/crm-quote-user-field-get.html',
+ 'Returns a userfield for quote by ID.'
+ )]
+ public function get(int $userfieldItemId): QuoteUserfieldResult
+ {
+ return new QuoteUserfieldResult(
+ $this->core->call(
+ 'crm.quote.userfield.get',
+ [
+ 'id' => $userfieldItemId,
+ ]
+ )
+ );
+ }
+
+ /**
+ * Updates an existing user field for quotes.
+ *
+ *
+ * @throws BaseException
+ * @throws TransportException
+ * @link https://apidocs.bitrix24.com/api-reference/crm/quote/user-field/crm-quote-user-field-update.html
+ */
+ #[ApiEndpointMetadata(
+ 'crm.quote.userfield.update',
+ 'https://apidocs.bitrix24.com/api-reference/crm/quote/user-field/crm-quote-user-field-update.html',
+ 'Updates an existing user field for quotes.'
+ )]
+ public function update(int $userfieldItemId, array $userfieldFieldsToUpdate): UpdatedItemResult
+ {
+ return new UpdatedItemResult(
+ $this->core->call(
+ 'crm.quote.userfield.update',
+ [
+ 'id' => $userfieldItemId,
+ 'fields' => $userfieldFieldsToUpdate,
+ ]
+ )
+ );
+ }
+}
diff --git a/tests/CustomAssertions/CustomBitrix24Assertions.php b/tests/CustomAssertions/CustomBitrix24Assertions.php
index 4635474f..2edecd92 100644
--- a/tests/CustomAssertions/CustomBitrix24Assertions.php
+++ b/tests/CustomAssertions/CustomBitrix24Assertions.php
@@ -141,7 +141,6 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
break;
case 'user':
case 'crm_enum_ownertype':
- case 'crm_lead':
case 'integer':
case 'int':
$this->assertTrue(
@@ -227,6 +226,9 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
case 'object':
case 'crm_company':
case 'crm_contact':
+ case 'crm_deal':
+ case 'crm_lead':
+ case 'location':
case 'product_file':
if (str_contains($fieldCode, '_IDS') ||
str_contains($fieldCode, 'PHOTO') ||
diff --git a/tests/Integration/Services/CRM/Quote/Service/BatchTest.php b/tests/Integration/Services/CRM/Quote/Service/BatchTest.php
new file mode 100644
index 00000000..c18ac093
--- /dev/null
+++ b/tests/Integration/Services/CRM/Quote/Service/BatchTest.php
@@ -0,0 +1,113 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Tests\Integration\Services\CRM\Quote\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\CRM\Quote\Service\Quote;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Class BatchTest
+ *
+ * @package Bitrix24\SDK\Tests\Integration\Services\CRM\Quote\Service
+ */
+#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\CRM\Quote\Service\Batch::class)]
+class BatchTest extends TestCase
+{
+ protected Quote $quoteService;
+
+
+ protected function setUp(): void
+ {
+ $this->quoteService = Fabric::getServiceBuilder()->getCRMScope()->quote();
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch list quotes')]
+ public function testBatchList(): void
+ {
+ $itemId = $this->quoteService->add(['TITLE' => 'test quote'])->getId();
+ $cnt = 0;
+
+ foreach ($this->quoteService->batch->list([], ['ID' => $itemId], ['ID', 'NAME'], 1) as $item) {
+ $cnt++;
+ }
+
+ self::assertGreaterThanOrEqual(1, $cnt);
+
+ $this->quoteService->delete($itemId);
+ }
+
+ /**
+ * @throws \Bitrix24\SDK\Core\Exceptions\BaseException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch add quote')]
+ public function testBatchAdd(): void
+ {
+ $items = [];
+ for ($i = 1; $i < 60; $i++) {
+ $items[] = ['TITLE' => 'TITLE-' . $i];
+ }
+
+ $cnt = 0;
+ $itemId = [];
+ foreach ($this->quoteService->batch->add($items) as $item) {
+ $cnt++;
+ $itemId[] = $item->getId();
+ }
+
+ self::assertEquals(count($items), $cnt);
+
+ $cnt = 0;
+ foreach ($this->quoteService->batch->delete($itemId) as $cnt => $deleteResult) {
+ $cnt++;
+ }
+
+ self::assertEquals(count($items), $cnt);
+ }
+
+ /**
+ * @throws \Bitrix24\SDK\Core\Exceptions\BaseException
+ */
+ #[\PHPUnit\Framework\Attributes\TestDox('Batch delete quotes')]
+ public function testBatchDelete(): void
+ {
+ $quotes = [];
+ for ($i = 1; $i < 60; $i++) {
+ $quotes[] = ['TITLE' => 'TITLE-' . $i];
+ }
+
+ $cnt = 0;
+ $itemId = [];
+ foreach ($this->quoteService->batch->add($quotes) as $item) {
+ $cnt++;
+ $itemId[] = $item->getId();
+ }
+
+ self::assertEquals(count($quotes), $cnt);
+
+ $cnt = 0;
+ foreach ($this->quoteService->batch->delete($itemId) as $cnt => $deleteResult) {
+ $cnt++;
+ }
+
+ self::assertEquals(count($quotes), $cnt);
+ }
+
+}
diff --git a/tests/Integration/Services/CRM/Quote/Service/QuoteContactTest.php b/tests/Integration/Services/CRM/Quote/Service/QuoteContactTest.php
new file mode 100644
index 00000000..3e5a0718
--- /dev/null
+++ b/tests/Integration/Services/CRM/Quote/Service/QuoteContactTest.php
@@ -0,0 +1,173 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Tests\Integration\Services\CRM\Quote\Service;
+
+
+use Bitrix24\SDK\Services\CRM\Common\ContactConnection;
+use Bitrix24\SDK\Services\CRM\Quote\Service\QuoteContact;
+use Bitrix24\SDK\Services\ServiceBuilder;
+use Bitrix24\SDK\Tests\Builders\Services\CRM\ContactBuilder;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\CoversMethod;
+use PHPUnit\Framework\TestCase;
+use Bitrix24\SDK\Core;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+
+#[CoversClass(QuoteContact::class)]
+#[CoversMethod(QuoteContact::class, 'fields')]
+#[CoversMethod(QuoteContact::class, 'setItems')]
+#[CoversMethod(QuoteContact::class, 'deleteItems')]
+#[CoversMethod(QuoteContact::class, 'get')]
+#[CoversMethod(QuoteContact::class, 'add')]
+#[CoversMethod(QuoteContact::class, 'delete')]
+class QuoteContactTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+
+ private ServiceBuilder $sb;
+
+ private array $createdQuotes = [];
+
+ private array $createdContacts = [];
+
+ protected function setUp(): void
+ {
+ $this->sb = Fabric::getServiceBuilder();
+ }
+
+ protected function tearDown(): void
+ {
+ foreach ($this->sb->getCRMScope()->quote()->batch->delete($this->createdQuotes) as $result) {
+ // ###
+ }
+
+ foreach ($this->sb->getCRMScope()->contact()->batch->delete($this->createdContacts) as $result) {
+ // ###
+ }
+ }
+
+ public function testSet(): void
+ {
+ $quoteId = $this->sb->getCRMScope()->quote()->add(['TITLE' => 'test quote'])->getId();
+ $this->createdQuotes[] = $quoteId;
+
+ $contactIdOne = $this->sb->getCRMScope()->contact()->add((new ContactBuilder())->build())->getId();
+ $this->createdContacts[] = $contactIdOne;
+ $contactIdTwo = $this->sb->getCRMScope()->contact()->add((new ContactBuilder())->build())->getId();
+ $this->createdContacts[] = $contactIdTwo;
+
+ $this->sb->getCRMScope()->quoteContact()->setItems($quoteId, [
+ new ContactConnection($contactIdOne, 100, true),
+ new ContactConnection($contactIdTwo, 100, false),
+ ]);
+
+ $connectedId = [$contactIdOne, $contactIdTwo];
+ $connectedContacts = $this->sb->getCRMScope()->quoteContact()->get($quoteId)->getContactConnections();
+
+ foreach ($connectedContacts as $connectedContact) {
+ $this->assertContains($connectedContact->CONTACT_ID, $connectedId);
+ }
+ }
+
+ public function testAdd(): void
+ {
+ $quoteId = $this->sb->getCRMScope()->quote()->add(['TITLE' => 'test quote'])->getId();
+ $this->createdQuotes[] = $quoteId;
+
+ $contactIdOne = $this->sb->getCRMScope()->contact()->add((new ContactBuilder())->build())->getId();
+ $this->createdContacts[] = $contactIdOne;
+
+ $this->assertTrue(
+ $this->sb->getCRMScope()->quoteContact()->add(
+ $quoteId,
+ new ContactConnection($contactIdOne, 100, true)
+ )->isSuccess()
+ );
+
+ $contactIdTwo = $this->sb->getCRMScope()->contact()->add((new ContactBuilder())->build())->getId();
+ $this->createdContacts[] = $contactIdTwo;
+
+ $this->assertTrue(
+ $this->sb->getCRMScope()->quoteContact()->add(
+ $quoteId,
+ new ContactConnection($contactIdTwo, 100, true)
+ )->isSuccess()
+ );
+
+ $connectedId = [$contactIdOne, $contactIdTwo];
+ $connectedContacts = $this->sb->getCRMScope()->quoteContact()->get($quoteId)->getContactConnections();
+
+ foreach ($connectedContacts as $connectedContact) {
+ $this->assertContains($connectedContact->CONTACT_ID, $connectedId);
+ }
+ }
+
+ public function testDeleteItems(): void
+ {
+ $quoteId = $this->sb->getCRMScope()->quote()->add(['TITLE' => 'test quote'])->getId();
+ $this->createdQuotes[] = $quoteId;
+
+ $contactIdOne = $this->sb->getCRMScope()->contact()->add((new ContactBuilder())->build())->getId();
+ $this->createdContacts[] = $contactIdOne;
+ $contactIdTwo = $this->sb->getCRMScope()->contact()->add((new ContactBuilder())->build())->getId();
+ $this->createdContacts[] = $contactIdTwo;
+
+ $this->assertTrue(
+ $this->sb->getCRMScope()->quoteContact()->setItems($quoteId, [
+ new ContactConnection($contactIdOne, 100, true),
+ new ContactConnection($contactIdTwo, 100, false),
+ ])->isSuccess()
+ );
+
+ $this->assertTrue($this->sb->getCRMScope()->quoteContact()->deleteItems($quoteId)->isSuccess());
+
+ $this->assertCount(0, $this->sb->getCRMScope()->quoteContact()->get($quoteId)->getContactConnections());
+ }
+
+ public function testDelete(): void
+ {
+ $quoteId = $this->sb->getCRMScope()->quote()->add(['TITLE' => 'test quote'])->getId();
+ $this->createdQuotes[] = $quoteId;
+
+ $contactIdOne = $this->sb->getCRMScope()->contact()->add((new ContactBuilder())->build())->getId();
+ $this->createdContacts[] = $contactIdOne;
+ $contactIdTwo = $this->sb->getCRMScope()->contact()->add((new ContactBuilder())->build())->getId();
+ $this->createdContacts[] = $contactIdTwo;
+
+ $this->assertTrue(
+ $this->sb->getCRMScope()->quoteContact()->setItems($quoteId, [
+ new ContactConnection($contactIdOne, 100, true),
+ new ContactConnection($contactIdTwo, 100, false),
+ ])->isSuccess()
+ );
+
+ $this->assertTrue($this->sb->getCRMScope()->quoteContact()->delete($quoteId, $contactIdTwo)->isSuccess());
+
+ $this->assertCount(1, $this->sb->getCRMScope()->quoteContact()->get($quoteId)->getContactConnections());
+ }
+
+ public function testSetWithEmptyConnections(): void
+ {
+ $this->expectException(Core\Exceptions\InvalidArgumentException::class);
+ $this->sb->getCRMScope()->quoteContact()->setItems(1, []);
+ }
+
+ public function testSetWithWrongType(): void
+ {
+ $this->expectException(Core\Exceptions\InvalidArgumentException::class);
+ /** @phpstan-ignore-next-line */
+ $this->sb->getCRMScope()->quoteContact()->setItems(1, [new \DateTime()]);
+ }
+}
\ No newline at end of file
diff --git a/tests/Integration/Services/CRM/Quote/Service/QuoteProductRowsTest.php b/tests/Integration/Services/CRM/Quote/Service/QuoteProductRowsTest.php
new file mode 100644
index 00000000..b31217fb
--- /dev/null
+++ b/tests/Integration/Services/CRM/Quote/Service/QuoteProductRowsTest.php
@@ -0,0 +1,147 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Tests\Integration\Services\CRM\Quote\Service;
+
+use Money\Currencies\ISOCurrencies;
+use Money\Currency;
+use Money\Formatter\DecimalMoneyFormatter;
+use Money\Money;
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\CRM\Common\Result\DiscountType;
+use Bitrix24\SDK\Services\CRM\Quote\Result\QuoteProductRowItemResult;
+use Bitrix24\SDK\Services\CRM\Quote\Service\Quote;
+use Bitrix24\SDK\Services\CRM\Quote\Service\QuoteProductRows;
+use Bitrix24\SDK\Tests\Builders\DemoDataGenerator;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use MoneyPHP\Percentage\Percentage;
+use PHPUnit\Framework\TestCase;
+use Typhoon\Reflection\TyphoonReflector;
+
+#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\CRM\Quote\Service\QuoteProductRows::class)]
+class QuoteProductRowsTest extends TestCase
+{
+ private Quote $quoteService;
+
+ private QuoteProductRows $quoteProductRowsService;
+
+ private DecimalMoneyFormatter $decimalMoneyFormatter;
+
+ private TyphoonReflector $typhoonReflector;
+
+ protected function setUp(): void
+ {
+ $this->quoteService = Fabric::getServiceBuilder()->getCRMScope()->quote();
+ $this->quoteProductRowsService = Fabric::getServiceBuilder()->getCRMScope()->quoteProductRows();
+ $this->decimalMoneyFormatter = new DecimalMoneyFormatter(new ISOCurrencies());
+ $this->typhoonReflector = TyphoonReflector::build();
+ }
+
+ public function testAllSystemPropertiesAnnotated(): void
+ {
+ $quoteId = $this->quoteService->add(['TITLE' => 'test quote'])->getId();
+ $this->quoteProductRowsService->set(
+ $quoteId,
+ [
+ [
+ 'PRODUCT_NAME' => sprintf('product name %s', time()),
+ 'PRICE' => $this->decimalMoneyFormatter->format(new Money(100000, DemoDataGenerator::getCurrency())),
+ ],
+ ]
+ );
+ // get response from server with actual keys
+ $propListFromApi = array_keys($this->quoteProductRowsService->get($quoteId)->getCoreResponse()->getResponseData()->getResult()['result']['rows'][0]);
+ // parse keys from phpdoc annotation
+ $collection = $this->typhoonReflector->reflectClass(QuoteProductRowItemResult::class)->properties();
+ $propsFromAnnotations = [];
+ foreach ($collection as $meta) {
+ if ($meta->isAnnotated() && !$meta->isNative()) {
+ $propsFromAnnotations[] = $meta->id->name;
+ }
+ }
+
+ $this->assertEquals($propListFromApi, $propsFromAnnotations,
+ sprintf('in phpdocs annotations for class %s cant find fields from actual api response: %s',
+ QuoteProductRowItemResult::class,
+ implode(', ', array_values(array_diff($propListFromApi, $propsFromAnnotations)))
+ ));
+
+ $this->quoteService->delete($quoteId);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testSet(): void
+ {
+ $quoteId = $this->quoteService->add(['TITLE' => sprintf('test quote %s', time())])->getId();
+ $quote = $this->quoteService->get($quoteId)->quote();
+ $price = new Money(100000, $quote->CURRENCY_ID);
+ $discount = new Money(50012, $quote->CURRENCY_ID);
+ $this::assertTrue(
+ $this->quoteProductRowsService->set(
+ $quoteId,
+ [
+ [
+ 'PRODUCT_NAME' => sprintf('product name %s', time()),
+ 'PRICE' => $this->decimalMoneyFormatter->format($price),
+ 'DISCOUNT_TYPE_ID' => 1,
+ 'DISCOUNT_SUM' => $this->decimalMoneyFormatter->format($discount)
+ ],
+ ]
+ )->isSuccess()
+ );
+ $quoteProductRowItemsResult = $this->quoteProductRowsService->get($quoteId);
+ $this->assertCount(1, $quoteProductRowItemsResult->getProductRows());
+ $productRow = $quoteProductRowItemsResult->getProductRows()[0];
+ $this->assertEquals($price, $productRow->PRICE);
+ $this->assertEquals(DiscountType::monetary, $productRow->DISCOUNT_TYPE_ID);
+ $this->assertEquals($discount, $productRow->DISCOUNT_SUM);
+ $discount = $discount->multiply(100)->divide($this->decimalMoneyFormatter->format($price->add($discount)));
+ $calculatedPercentage = new Percentage((string)((int)$discount->getAmount() / 100));
+ $this->assertEquals($calculatedPercentage, $productRow->DISCOUNT_RATE);
+
+ $this->quoteService->delete($quoteId);
+ }
+
+ public function testGet(): void
+ {
+ $quoteId = $this->quoteService->add(['TITLE' => sprintf('test quote %s', time())])->getId();
+ $quote = $this->quoteService->get($quoteId)->quote();
+ $price = new Money(100000, $quote->CURRENCY_ID);
+ $discount = new Money(0, $quote->CURRENCY_ID);
+ $this::assertTrue(
+ $this->quoteProductRowsService->set(
+ $quoteId,
+ [
+ [
+ 'PRODUCT_NAME' => sprintf('product name %s', time()),
+ 'PRICE' => $this->decimalMoneyFormatter->format($price),
+ ],
+ ]
+ )->isSuccess()
+ );
+ $quoteProductRowItemsResult = $this->quoteProductRowsService->get($quoteId);
+ $this->assertCount(1, $quoteProductRowItemsResult->getProductRows());
+ $productRow = $quoteProductRowItemsResult->getProductRows()[0];
+ $this->assertEquals($price, $productRow->PRICE);
+ $this->assertEquals(DiscountType::percentage, $productRow->DISCOUNT_TYPE_ID);
+ $this->assertEquals($discount, $productRow->DISCOUNT_SUM);
+ $this->assertEquals(Percentage::zero(), $productRow->DISCOUNT_RATE);
+
+ $this->quoteService->delete($quoteId);
+ }
+
+}
diff --git a/tests/Integration/Services/CRM/Quote/Service/QuoteTest.php b/tests/Integration/Services/CRM/Quote/Service/QuoteTest.php
new file mode 100644
index 00000000..ac6cf2ad
--- /dev/null
+++ b/tests/Integration/Services/CRM/Quote/Service/QuoteTest.php
@@ -0,0 +1,153 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Tests\Integration\Services\CRM\Quote\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Core;
+use Bitrix24\SDK\Services\CRM\Quote\Result\QuoteItemResult;
+use Bitrix24\SDK\Services\CRM\Quote\Service\Quote;
+use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\Attributes\CoversFunction;
+use PHPUnit\Framework\Attributes\CoversMethod;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Class QuoteTest
+ *
+ * @package Bitrix24\SDK\Tests\Integration\Services\CRM\Quote\Service
+ */
+#[CoversMethod(Quote::class,'add')]
+#[CoversMethod(Quote::class,'delete')]
+#[CoversMethod(Quote::class,'get')]
+#[CoversMethod(Quote::class,'list')]
+#[CoversMethod(Quote::class,'fields')]
+#[CoversMethod(Quote::class,'update')]
+#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\CRM\Quote\Service\Quote::class)]
+class QuoteTest extends TestCase
+{
+ use CustomBitrix24Assertions;
+ protected Quote $quoteService;
+
+ protected function setUp(): void
+ {
+ $this->quoteService = Fabric::getServiceBuilder()->getCRMScope()->quote();
+ }
+
+ public function testAllSystemFieldsAnnotated(): void
+ {
+ $propListFromApi = (new Core\Fields\FieldsFilter())->filterSystemFields(array_keys($this->quoteService->fields()->getFieldsDescription()));
+ $this->assertBitrix24AllResultItemFieldsAnnotated($propListFromApi, QuoteItemResult::class);
+ }
+
+ public function testAllSystemFieldsHasValidTypeAnnotation():void
+ {
+ $allFields = $this->quoteService->fields()->getFieldsDescription();
+ $systemFieldsCodes = (new Core\Fields\FieldsFilter())->filterSystemFields(array_keys($allFields));
+ $systemFields = array_filter($allFields, static fn($code): bool => in_array($code, $systemFieldsCodes, true), ARRAY_FILTER_USE_KEY);
+
+ $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation(
+ $systemFields,
+ QuoteItemResult::class);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testAdd(): void
+ {
+ self::assertGreaterThan(1, $this->quoteService->add(['TITLE' => 'test quote'])->getId());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testDelete(): void
+ {
+ self::assertTrue($this->quoteService->delete($this->quoteService->add(['TITLE' => 'test quote 1'])->getId())->isSuccess());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testFields(): void
+ {
+ self::assertIsArray($this->quoteService->fields()->getFieldsDescription());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testGet(): void
+ {
+ self::assertGreaterThan(
+ 1,
+ $this->quoteService->get($this->quoteService->add(['TITLE' => 'test Quote 2'])->getId())->quote()->ID
+ );
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testList(): void
+ {
+ $this->quoteService->add(['TITLE' => 'test']);
+ self::assertGreaterThanOrEqual(1, $this->quoteService->list([], [], ['ID', 'TITLE'])->getQuotes());
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testUpdate(): void
+ {
+ $addedItemResult = $this->quoteService->add(['TITLE' => 'test quote']);
+ $newTitle = 'test2';
+
+ self::assertTrue($this->quoteService->update($addedItemResult->getId(), ['TITLE' => $newTitle])->isSuccess());
+ self::assertEquals($newTitle, $this->quoteService->get($addedItemResult->getId())->quote()->TITLE);
+ }
+
+ /**
+ * @throws \Bitrix24\SDK\Core\Exceptions\BaseException
+ * @throws \Bitrix24\SDK\Core\Exceptions\TransportException
+ */
+ public function testCountByFilter(): void
+ {
+ $before = $this->quoteService->countByFilter();
+
+ $newItemsCount = 20;
+ $items = [];
+ for ($i = 1; $i <= $newItemsCount; $i++) {
+ $items[] = ['TITLE' => 'TITLE-' . $i];
+ }
+
+ $cnt = 0;
+ foreach ($this->quoteService->batch->add($items) as $item) {
+ $cnt++;
+ }
+
+ self::assertEquals(count($items), $cnt);
+
+ $after = $this->quoteService->countByFilter();
+
+ $this->assertEquals($before + $newItemsCount, $after);
+ }
+}
diff --git a/tests/Integration/Services/CRM/Quote/Service/QuoteUserfieldTest.php b/tests/Integration/Services/CRM/Quote/Service/QuoteUserfieldTest.php
new file mode 100644
index 00000000..78a16023
--- /dev/null
+++ b/tests/Integration/Services/CRM/Quote/Service/QuoteUserfieldTest.php
@@ -0,0 +1,108 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Tests\Integration\Services\CRM\Quote\Service;
+
+use Bitrix24\SDK\Services\CRM\Quote\Service\QuoteUserfield;
+use Bitrix24\SDK\Tests\Builders\Services\CRM\Userfield\SystemUserfieldBuilder;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use Generator;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\CoversMethod;
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\TestCase;
+
+#[CoversClass(QuoteUserfield::class)]
+#[CoversMethod(QuoteUserfield::class, 'add')]
+#[CoversMethod(QuoteUserfield::class, 'get')]
+#[CoversMethod(QuoteUserfield::class, 'list')]
+#[CoversMethod(QuoteUserfield::class, 'delete')]
+#[CoversMethod(QuoteUserfield::class, 'update')]
+class QuoteUserfieldTest extends TestCase
+{
+ protected QuoteUserfield $userfieldService;
+
+ protected function setUp(): void
+ {
+ $this->userfieldService = Fabric::getServiceBuilder()->getCRMScope()->quoteUserfield();
+ }
+
+ /**
+ * @throws \Exception
+ */
+ public static function systemUserfieldsDemoDataDataProvider(): Generator
+ {
+ yield 'user type id string' => [
+ (new SystemUserfieldBuilder())->build(),
+ ];
+
+ mt_srand();
+ yield 'user type id integer' => [
+ (new SystemUserfieldBuilder('integer'))->build(),
+ ];
+ }
+
+ #[DataProvider('systemUserfieldsDemoDataDataProvider')]
+ public function testAdd(array $newUserFieldItem): void
+ {
+ self::assertGreaterThanOrEqual(1, $this->userfieldService->add($newUserFieldItem)->getId());
+ }
+
+ #[DataProvider('systemUserfieldsDemoDataDataProvider')]
+ public function testDelete(array $newUserFieldItem): void
+ {
+ $newUserfieldId = $this->userfieldService->add($newUserFieldItem)->getId();
+ $this->assertTrue($this->userfieldService->delete($newUserfieldId)->isSuccess());
+ }
+
+ #[DataProvider('systemUserfieldsDemoDataDataProvider')]
+ public function testGet(array $newUserFieldItem): void
+ {
+ $newUserfieldId = $this->userfieldService->add($newUserFieldItem)->getId();
+ $quoteUserfieldItemResult = $this->userfieldService->get($newUserfieldId)->userfieldItem();
+ $this->assertEquals($newUserfieldId, $quoteUserfieldItemResult->ID);
+ $this->assertEquals($newUserFieldItem['USER_TYPE_ID'], $quoteUserfieldItemResult->USER_TYPE_ID);
+ $this->assertEquals('UF_CRM_' . $newUserFieldItem['FIELD_NAME'], $quoteUserfieldItemResult->FIELD_NAME);
+ $this->assertEquals($newUserFieldItem['XML_ID'], $quoteUserfieldItemResult->XML_ID);
+ }
+
+ #[DataProvider('systemUserfieldsDemoDataDataProvider')]
+ public function testUpdate(array $newUserFieldItem): void
+ {
+ $newUserfieldId = $this->userfieldService->add($newUserFieldItem)->getId();
+ $quoteUserfieldItemResult = $this->userfieldService->get($newUserfieldId)->userfieldItem();
+ $this->assertEquals($newUserfieldId, $quoteUserfieldItemResult->ID);
+ $this->assertEquals($newUserFieldItem['USER_TYPE_ID'], $quoteUserfieldItemResult->USER_TYPE_ID);
+ $this->assertEquals('UF_CRM_' . $newUserFieldItem['FIELD_NAME'], $quoteUserfieldItemResult->FIELD_NAME);
+ $this->assertEquals($newUserFieldItem['XML_ID'], $quoteUserfieldItemResult->XML_ID);
+
+ $this->assertTrue(
+ $this->userfieldService->update(
+ $newUserfieldId,
+ [
+ 'EDIT_FORM_LABEL' => $newUserFieldItem['EDIT_FORM_LABEL']['en'] . 'QQQ',
+ ]
+ )->isSuccess()
+ );
+
+ $ufFieldAfter = $this->userfieldService->get($newUserfieldId)->userfieldItem();
+ $this->assertEquals($quoteUserfieldItemResult->EDIT_FORM_LABEL['en'] . 'QQQ', $ufFieldAfter->EDIT_FORM_LABEL['en']);
+ }
+
+ public function testList(): void
+ {
+ $quoteUserfieldsResult = $this->userfieldService->list([], []);
+ $this->assertGreaterThanOrEqual(0, count($quoteUserfieldsResult->getUserfields()));
+ }
+
+}
diff --git a/tests/Integration/Services/CRM/Quote/Service/QuoteUserfieldUseCaseTest.php b/tests/Integration/Services/CRM/Quote/Service/QuoteUserfieldUseCaseTest.php
new file mode 100644
index 00000000..8d0badab
--- /dev/null
+++ b/tests/Integration/Services/CRM/Quote/Service/QuoteUserfieldUseCaseTest.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the MIT-LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Bitrix24\SDK\Tests\Integration\Services\CRM\Quote\Service;
+
+use Bitrix24\SDK\Core\Exceptions\BaseException;
+use Bitrix24\SDK\Core\Exceptions\TransportException;
+use Bitrix24\SDK\Services\CRM\Quote\Service\Quote;
+use Bitrix24\SDK\Services\CRM\Quote\Service\QuoteUserfield;
+use Bitrix24\SDK\Tests\Integration\Fabric;
+use PHPUnit\Framework\TestCase;
+
+#[\PHPUnit\Framework\Attributes\CoversClass(\Bitrix24\SDK\Services\CRM\Quote\Service\QuoteUserfield::class)]
+class QuoteUserfieldUseCaseTest extends TestCase
+{
+ protected Quote $quoteService;
+
+ protected QuoteUserfield $quoteUserfieldService;
+
+ protected int $quoteUserfieldId;
+
+ /**
+ * @throws \Bitrix24\SDK\Services\CRM\Userfield\Exceptions\UserfieldNameIsTooLongException
+ * @throws \Bitrix24\SDK\Core\Exceptions\TransportException
+ * @throws \Bitrix24\SDK\Core\Exceptions\InvalidArgumentException
+ * @throws \Bitrix24\SDK\Core\Exceptions\BaseException
+ */
+ protected function setUp(): void
+ {
+ $this->quoteService = Fabric::getServiceBuilder()->getCRMScope()->quote();
+ $this->quoteUserfieldService = Fabric::getServiceBuilder()->getCRMScope()->quoteUserfield();
+
+ $this->quoteUserfieldId = $this->quoteUserfieldService->add(
+ [
+ 'FIELD_NAME' => sprintf('%s%s', substr((string)random_int(0, PHP_INT_MAX), 0, 3), time()),
+ 'EDIT_FORM_LABEL' => [
+ 'ru' => 'тест uf тип string',
+ 'en' => 'test uf type string',
+ ],
+ 'LIST_COLUMN_LABEL' => [
+ 'ru' => 'тест uf тип string',
+ 'en' => 'test uf type string',
+ ],
+ 'USER_TYPE_ID' => 'string',
+ 'XML_ID' => 'b24phpsdk_type_string',
+ 'SETTINGS' => [
+ 'DEFAULT_VALUE' => 'hello world',
+ ],
+ ]
+ )->getId();
+ }
+
+ protected function tearDown(): void
+ {
+ $this->quoteUserfieldService->delete($this->quoteUserfieldId);
+ }
+
+ /**
+ * @throws BaseException
+ * @throws TransportException
+ */
+ public function testOperationsWithUserfieldFromQuoteItem(): void
+ {
+ // get userfield metadata
+ $quoteUserfieldItemResult = $this->quoteUserfieldService->get($this->quoteUserfieldId)->userfieldItem();
+ $ufOriginalFieldName = $quoteUserfieldItemResult->getOriginalFieldName();
+ $ufFieldName = $quoteUserfieldItemResult->FIELD_NAME;
+
+ // add quote with uf value
+ $fieldNameValue = 'test field value';
+ $newQuoteId = $this->quoteService->add(
+ [
+ 'TITLE' => 'test quote',
+ $ufFieldName => $fieldNameValue,
+ ]
+ )->getId();
+ $quote = $this->quoteService->get($newQuoteId)->quote();
+ $this->assertEquals($fieldNameValue, $quote->getUserfieldByFieldName($ufOriginalFieldName));
+
+ // update quote userfield value
+ $newUfValue = 'test 2';
+ $this->assertTrue(
+ $this->quoteService->update(
+ $quote->ID,
+ [
+ $ufFieldName => $newUfValue,
+ ]
+ )->isSuccess()
+ );
+ $quoteItemResult = $this->quoteService->get($quote->ID)->quote();
+ $this->assertEquals($newUfValue, $quoteItemResult->getUserfieldByFieldName($ufOriginalFieldName));
+ }
+
+}