Repository: leshchenko1979/fast_bitrix24 (python)

Issue:
extract_from_batch_response
Проблема в методе `extract_from_batch_response`, в файле `fast_bitrix24/server_response.py`.

Код, который я выполняю:
```python
result = await bitrix_client.call(
    "crm.item.add",
    {
        "entityTypeId": 1,
        "fields": {
            "first_name": "first_name",
            "family_name": "family_name",
            "third_name": "third_name",
            "age": date(1999, 12, 12),
            "age_": 12,
            "user_tg_id": "user_tg_id",
            "phone": "phone",
        }
    }
)
print(result)
```

Вот ссылка на используемый мной метод Bitrix24 REST API:
https://dev.1c-bitrix.ru/rest_help/crm/dynamic/methodscrmitem/crm_item_add.php

Вот такой результат я получаю:
`"item"`

Следуя по шагам выполнения, выясняю, что в функцию `extract_from_batch_response` приходит данный словарь, что в целом приемлемый для меня ответ от api, и с которым я бы мог работать:
```
{'order0000000000': {'item': {'id': 46, 'xmlId': None, 'title': 'Лидерская программа #46', 'createdBy': 6511, 'updatedBy': 6511, 'movedBy': 6511, 'createdTime': '2024-05-20T09:01:39+03:00', 'updatedTime': '2024-05-20T09:01:39+03:00', 'movedTime': '2024-05-20T09:01:39+03:00', 'categoryId': 122, 'opened': 'N', 'stageId': 'DT133_122:NEW', 'previousStageId': '', 'begindate': '2024-05-20T03:00:00+03:00', 'closedate': '2024-05-27T03:00:00+03:00', 'companyId': None, 'contactId': None, 'opportunity': 0, 'isManualOpportunity': 'N', 'taxValue': 0, 'currencyId': 'RUB', 'opportunityAccount': 0, 'taxValueAccount': 0, 'accountCurrencyId': 'RUB', 'mycompanyId': None, 'sourceId': 'CALLBACK', 'sourceDescription': None, 'webformId': None, 'ufCRM_30__I': 'fio[0]', 'ufCRM_30__F': 'fio[1]', 'ufCrm_30_': '', 'ufCrm30Gorod': '', 'ufCRM_30__TG': 'None', 'ufCrm30Spes': '', 'ufCRM_30__DR': '1999-12-12T03:00:00+03:00', 'ufCrm30_1713945028': None, 'ufCrm30_1713945184': None, 'ufCrm30_1713945484': None, 'ufCrm30_1713945543': None, 'ufCrm30_1713945562': None, 'ufCrm30_1713956150473': None, 'ufCRM_30_1714103168': '25', 'ufCrm30_1714103555': None, 'ufCrm30_1714103618': '', 'ufCrm30_1714103630': 'N', 'ufCrm30_1714104884176': None, 'ufCrm30_1714106355377': None, 'ufCrm30_1714111271': None, 'ufCrm30_1714112298027': None, 'ufCrm30_1714113441': None, 'ufCRM_30_1714122056': 'fio[2]', 'ufCrm30_1714131633': None, 'ufCrm30_1714131799': None, 'ufCrm30_1714131815': 'N', 'ufCrm30_1714131831': '', 'ufCRM_30_1714131841': 0, 'ufCrm30_1714204151': None, 'assignedById': 6511, 'lastActivityBy': 6511, 'lastActivityTime': '2024-05-20T09:01:39+03:00', 'utmSource': None, 'utmMedium': None, 'utmCampaign': None, 'utmContent': None, 'utmTerm': None, 'observers': [], 'contactIds': [], 'entityTypeId': 133}}}
```
однако после условия:
```if self.is_nested(first_item) or isinstance(first_item, list):```
результатом метода становится один из ключей `"item"`.

Конкретно, весь словарь обрезается в момент:
```result_list = list(chain(*result_list))```
Избавившись от этого преобразования, у меня получилось получить то, что я хотел. Но я понимаю, что в этой части кода был какой-то смысл, поэтому жду ответа от вас. 

Думаю проблема в том, что это взаимодействие с каким-то кастомным смарт-процессом, и подобный вывод не был предусмотрен в разработки библиотеки.

Pull request description:
Исправить и упростить обработку вложенных результатов с сервера
Fixes #235

Patch:
diff --git a/fast_bitrix24/mult_request.py b/fast_bitrix24/mult_request.py
index e4126e8..a4e2233 100644
--- a/fast_bitrix24/mult_request.py
+++ b/fast_bitrix24/mult_request.py
@@ -103,7 +103,7 @@ class MultipleServerRequestHandler:
             elif isinstance(extracted, dict):
                 self.results.update(extracted)
 
-            extracted_len += len(extracted)
+            extracted_len += len(extracted) if isinstance(extracted, list) else 1
 
         return extracted_len
 
diff --git a/fast_bitrix24/server_response.py b/fast_bitrix24/server_response.py
index 1f22bfa..922d514 100644
--- a/fast_bitrix24/server_response.py
+++ b/fast_bitrix24/server_response.py
@@ -70,29 +70,19 @@ class ServerResponseParser:
         return isinstance(self.result, dict) and "result" in self.result
 
     def extract_from_single_response(self, result: dict):
-        # если результат вызова содержит только словарь {ключ: список},
-        # то вернуть этот список.
+        # если результат вызова содержит только словарь из одного элемента {ключ: содержимое},
+        # то вернуть это содержимое.
         # См. https://github.com/leshchenko1979/fast_bitrix24/issues/132
 
         # метод `crm.stagehistory.list` возвращает dict["items", list] --
         # разворачиваем его в список
 
-        if self.is_nested(result):
-            contents = self.extract_from_nested(result)
-            if isinstance(contents, list):
-                return contents
-
-        return result
+        return next(iter(result.values())) if self.is_nested(result) else result
 
     @staticmethod
     def is_nested(result) -> bool:
         return isinstance(result, dict) and len(result) == 1
 
-    @staticmethod
-    def extract_from_nested(result: dict):
-        return next(iter(result.values()))
-
-
     def extract_from_batch_response(self, result) -> list:
 
         if not result:
@@ -111,7 +101,9 @@ class ServerResponseParser:
                 for element in result.values()
             ]
 
-            result_list = list(chain(*result_list))
+            # если получился вложенный список, то уплощаем
+            if isinstance(result_list[0], list):
+                result_list = list(chain(*result_list))
 
             return result_list
 
diff --git a/fast_bitrix24/user_request.py b/fast_bitrix24/user_request.py
index 26fc7d0..721926e 100644
--- a/fast_bitrix24/user_request.py
+++ b/fast_bitrix24/user_request.py
@@ -327,13 +327,12 @@ class CallUserRequest(GetByIDUserRequest):
             get_by_ID=False,
         ).run()
 
-        if isinstance(raw_results, dict):
-            results = tuple(raw_results.values())
+        if isinstance(raw_results, dict) and not is_single_item:
+            return tuple(raw_results.values())
+        elif raw_results and isinstance(raw_results, list) and is_single_item:
+            return raw_results[0]
         else:
-            # бывают случаи, что возвращается список
-            results = raw_results
-
-        return results[0] if results and is_single_item else results
+            return raw_results
 
     def prepare_item_list(self):
         # При отправке батчей на сервер результаты приходят не в том порядке,


Test patch:
diff --git a/tests/real_responses/crm_item_add.py b/tests/real_responses/crm_item_add.py
new file mode 100644
index 0000000..5f2c500
--- /dev/null
+++ b/tests/real_responses/crm_item_add.py
@@ -0,0 +1,41 @@
+response = {
+    "result": {
+        "result": {
+            "order0000000000": {
+                "item": {
+                    "id": 247,
+                    "xmlId": "",
+                    "title": "Андрей Макарцев\n",
+                    "createdBy": 6511,
+                    "updatedBy": 6511,
+                    "movedBy": 6511,
+                    "createdTime": "2024-05-21T15:14:26+03:00",
+                    "updatedTime": "2024-05-21T15:14:27+03:00",
+                    "movedTime": "2024-05-21T15:14:26+03:00",
+                    "entityTypeId": 133,
+                }
+            }
+        },
+        "result_error": [],
+        "result_total": [],
+        "result_next": [],
+        "result_time": {
+            "order0000000000": {
+                "start": 1716293667.476324,
+                "finish": 1716293667.52081,
+                "duration": 0.04448580741882324,
+                "processing": 0.04400205612182617,
+                "date_start": "2024-05-21T15:14:27+03:00",
+                "date_finish": "2024-05-21T15:14:27+03:00",
+            }
+        },
+    },
+    "time": {
+        "start": 1716293667.454244,
+        "finish": 1716293667.52083,
+        "duration": 0.06658601760864258,
+        "processing": 0.04455399513244629,
+        "date_start": "2024-05-21T15:14:27+03:00",
+        "date_finish": "2024-05-21T15:14:27+03:00",
+    },
+}
diff --git a/tests/test_server_responses.py b/tests/test_server_responses.py
index c173910..b52f738 100644
--- a/tests/test_server_responses.py
+++ b/tests/test_server_responses.py
@@ -260,3 +260,13 @@ def test_socialnetwork_api_workgroup_list(bx_dummy):
     )
 
     assert len(results) == 50
+
+
+def test_crm_add_item(bx_dummy):
+    from tests.real_responses.crm_item_add import response
+
+    bx_dummy.srh = MockSRH(response)
+    result = bx_dummy.call(
+        "crm.item.add", {"entityTypeId": 1, "fields": {"first_name": "first_name"}}
+    )
+    assert result != "item" and isinstance(result, dict) and "id" in result