spyreapi 0.0.2__tar.gz → 0.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spyreapi-0.0.4/MANIFEST.in +4 -0
- {spyreapi-0.0.2/src/spyreapi.egg-info → spyreapi-0.0.4}/PKG-INFO +36 -1
- spyreapi-0.0.4/README.md +57 -0
- {spyreapi-0.0.2 → spyreapi-0.0.4}/pyproject.toml +1 -1
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyre/Models/inventory_models.py +7 -3
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyre/Models/sales_models.py +1 -1
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyre/Models/shared_models.py +30 -1
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyre/client.py +83 -13
- spyreapi-0.0.4/src/spyre/crm.py +26 -0
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyre/customers.py +1 -1
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyre/inventory.py +1 -1
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyre/sales.py +58 -8
- spyreapi-0.0.4/src/spyre/spire.py +23 -0
- {spyreapi-0.0.2 → spyreapi-0.0.4/src/spyreapi.egg-info}/PKG-INFO +36 -1
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyreapi.egg-info/SOURCES.txt +2 -3
- spyreapi-0.0.2/MANIFEST.in +0 -3
- spyreapi-0.0.2/README.md +0 -22
- spyreapi-0.0.2/src/spyre/config.py +0 -8
- spyreapi-0.0.2/src/spyre/spire.py +0 -14
- spyreapi-0.0.2/tests/testing.py +0 -378
- {spyreapi-0.0.2 → spyreapi-0.0.4}/LICENSE +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.4}/setup.cfg +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyre/Exceptions.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyre/Models/__init__.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyre/Models/customers_models.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyre/__init__.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyre/utils.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyreapi.egg-info/dependency_links.txt +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyreapi.egg-info/requires.txt +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.4}/src/spyreapi.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spyreapi
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: A robust and extensible Python client for interacting with the [Spire Business Software API](https://developer.spiresystems.com/reference). This client provides an object-oriented interface to get, create, update, delete, query, filter, sort, and manage various Spire modules such as Sales Orders, Invoices, Inventory Items, and more.
|
|
5
5
|
Author-email: Sanjid Sharaf <sanjidsharaf1@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -42,3 +42,38 @@ A robust and extensible Python client for interacting with the [Spire Business S
|
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
44
|
pip install -r requirements.txt
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## ⚙️ Configuration
|
|
50
|
+
|
|
51
|
+
- How to set up your spire client
|
|
52
|
+
|
|
53
|
+
### Find your Spire URL
|
|
54
|
+
|
|
55
|
+
The base URL for the Spire API is the same url provided by Spire that you use to access Spire server and uses port 10880 as default:
|
|
56
|
+
Replace {spire-url} with the url provided by Spire.
|
|
57
|
+
|
|
58
|
+
- https://{spire-url}:10880/api/v2/
|
|
59
|
+
|
|
60
|
+
Spire Cloud
|
|
61
|
+
If you are using Spire cloud you do not need to specify a port. The base URL for API for Spire Cloud customers would be:
|
|
62
|
+
|
|
63
|
+
- https://{spire-cloud-url}/api/v2/
|
|
64
|
+
|
|
65
|
+
### Set up your client with your credentials
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from spyre import Spire
|
|
69
|
+
# host is your spire url and the port if applicable
|
|
70
|
+
client = Spire(host = 'your-spire-host', company = 'comapany-name' , username = 'username' , password = 'password' )
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Example : Updating the status of an inventory item
|
|
75
|
+
```python
|
|
76
|
+
item = client.inventory.items.get_item(1101) # Gets item with id 1101
|
|
77
|
+
item.status = 1 # Use either item. or item.model. . item.model. will bring up all attributes
|
|
78
|
+
item.update()
|
|
79
|
+
```
|
spyreapi-0.0.4/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Spire API Python Client
|
|
2
|
+
|
|
3
|
+
A robust and extensible Python client for interacting with the [Spire Business Software API](https://developer.spiresystems.com/reference). This client provides an object-oriented interface to get, create, update, delete, query, filter, sort, and manage various Spire modules such as Sales Orders, Invoices, Inventory Items, and more.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ✨ Features
|
|
8
|
+
|
|
9
|
+
- ✅ Object-oriented resource wrappers for each module (e.g., `salesOrder`, `invoice`, `item`)
|
|
10
|
+
- 🔍 Full-text search via `q` parameter
|
|
11
|
+
- 🔁 Pagination with `start` and `limit` support
|
|
12
|
+
- 🧾 JSON-based advanced filtering (supports `$gt`, `$lt`, `$in`, `$or`, etc.)
|
|
13
|
+
- ↕️ Multi-field sorting with ascending/descending control
|
|
14
|
+
- 🔧 Clean abstraction layer for API endpoints
|
|
15
|
+
- 📦 Powered by `pydantic` models for validation
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 📦 Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install -r requirements.txt
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## ⚙️ Configuration
|
|
28
|
+
|
|
29
|
+
- How to set up your spire client
|
|
30
|
+
|
|
31
|
+
### Find your Spire URL
|
|
32
|
+
|
|
33
|
+
The base URL for the Spire API is the same url provided by Spire that you use to access Spire server and uses port 10880 as default:
|
|
34
|
+
Replace {spire-url} with the url provided by Spire.
|
|
35
|
+
|
|
36
|
+
- https://{spire-url}:10880/api/v2/
|
|
37
|
+
|
|
38
|
+
Spire Cloud
|
|
39
|
+
If you are using Spire cloud you do not need to specify a port. The base URL for API for Spire Cloud customers would be:
|
|
40
|
+
|
|
41
|
+
- https://{spire-cloud-url}/api/v2/
|
|
42
|
+
|
|
43
|
+
### Set up your client with your credentials
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from spyre import Spire
|
|
47
|
+
# host is your spire url and the port if applicable
|
|
48
|
+
client = Spire(host = 'your-spire-host', company = 'comapany-name' , username = 'username' , password = 'password' )
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Example : Updating the status of an inventory item
|
|
53
|
+
```python
|
|
54
|
+
item = client.inventory.items.get_item(1101) # Gets item with id 1101
|
|
55
|
+
item.status = 1 # Use either item. or item.model. . item.model. will bring up all attributes
|
|
56
|
+
item.update()
|
|
57
|
+
```
|
|
@@ -37,7 +37,11 @@ class UnitOfMeasure(BaseModel):
|
|
|
37
37
|
|
|
38
38
|
class Pricing(BaseModel):
|
|
39
39
|
id: Optional[int] = None
|
|
40
|
-
|
|
40
|
+
sellPrice: Optional[List[str]] = None
|
|
41
|
+
currMargin: Optional[str] = None
|
|
42
|
+
currMarginPct: Optional[str] = None
|
|
43
|
+
avgMargin: Optional[str] = None
|
|
44
|
+
avgMarginPct: Optional[str] = None
|
|
41
45
|
|
|
42
46
|
|
|
43
47
|
class ItemUDF(BaseModel):
|
|
@@ -143,7 +147,7 @@ class InventoryItem(BaseModel):
|
|
|
143
147
|
manufactureCountry: Optional[str] = None
|
|
144
148
|
harmonizedCode: Optional[str] = None
|
|
145
149
|
extendedDescription: Optional[str] = None
|
|
146
|
-
pricing: Optional[Dict[str, Pricing]] = None
|
|
150
|
+
pricing: Optional[Union[ Pricing | Dict[str, Pricing]]] = None
|
|
147
151
|
salesTaxFlags: Optional[Dict[str, Union[str, int, float, bool]]] = None
|
|
148
152
|
images: Optional[List[str]] = None
|
|
149
153
|
defaultExpiryDate: Optional[str] = None
|
|
@@ -151,7 +155,7 @@ class InventoryItem(BaseModel):
|
|
|
151
155
|
upload: Optional[bool] = None
|
|
152
156
|
showOptions: Optional[bool] = None
|
|
153
157
|
lastModified: Optional[str] = None
|
|
154
|
-
levy: Optional[str] = None
|
|
158
|
+
levy: Optional[Union[str | dict]] = None
|
|
155
159
|
udf: Optional[ItemUDF] = None
|
|
156
160
|
createdBy: Optional[str] = None
|
|
157
161
|
modifiedBy: Optional[str] = None
|
|
@@ -15,7 +15,7 @@ class SalesOrderItem(BaseModel):
|
|
|
15
15
|
sequence: Optional[int] = None
|
|
16
16
|
parentSequence: Optional[int] = None
|
|
17
17
|
inventory: Optional[Inventory] = None
|
|
18
|
-
serials: Optional[str] = None
|
|
18
|
+
serials: Optional[List[str]] = None
|
|
19
19
|
whse: Optional[str] = None
|
|
20
20
|
partNo: Optional[str] = None
|
|
21
21
|
description: Optional[str] = None
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List, Optional, Dict, Union
|
|
1
|
+
from typing import List, Optional, Dict, Union, Any
|
|
2
2
|
from pydantic import BaseModel, model_validator
|
|
3
3
|
|
|
4
4
|
class PhoneFax(BaseModel):
|
|
@@ -94,3 +94,32 @@ class Address(BaseModel):
|
|
|
94
94
|
if isinstance(contacts, list) and len(contacts) > 3:
|
|
95
95
|
data['contacts'] = contacts[:3]
|
|
96
96
|
return data
|
|
97
|
+
|
|
98
|
+
class AssignedTo(BaseModel):
|
|
99
|
+
id: Optional[int] = None
|
|
100
|
+
uuid: Optional[str] = None
|
|
101
|
+
username: Optional[str] = None
|
|
102
|
+
|
|
103
|
+
class Note(BaseModel):
|
|
104
|
+
id: Optional[int] = None
|
|
105
|
+
linkTable: Optional[str] = None
|
|
106
|
+
linkNo: Optional[str] = None
|
|
107
|
+
subject: Optional[str] = None
|
|
108
|
+
body: Optional[str] = None
|
|
109
|
+
attachment: Optional[Any] = None
|
|
110
|
+
attachmentName: Optional[str] = None
|
|
111
|
+
dueDate: Optional[str] = None
|
|
112
|
+
completedDate: Optional[str] = None
|
|
113
|
+
attention: Optional[str] = None
|
|
114
|
+
type: Optional[str] = None
|
|
115
|
+
displayType: Optional[str] = None
|
|
116
|
+
assignedTo: Optional[AssignedTo] = None
|
|
117
|
+
groupType: Optional[str] = None
|
|
118
|
+
qty: Optional[int] = None
|
|
119
|
+
alert: Optional[Union[str, bool]] = None
|
|
120
|
+
print: Optional[Union[str, bool]] = None
|
|
121
|
+
created: Optional[str] = None
|
|
122
|
+
createdBy: Optional[str] = None
|
|
123
|
+
modified: Optional[str] = None
|
|
124
|
+
modifiedBy: Optional[str] = None
|
|
125
|
+
links: Optional[Dict[str, str]] = None
|
|
@@ -1,22 +1,46 @@
|
|
|
1
1
|
import requests
|
|
2
|
-
from .config import BASE_URL
|
|
3
2
|
from typing import TypeVar, Optional, Type, Generic, List, Union, Tuple, Dict, Any
|
|
4
3
|
from pydantic import BaseModel
|
|
5
4
|
import json
|
|
6
5
|
import urllib.parse
|
|
6
|
+
from requests.exceptions import HTTPError, ConnectionError, Timeout, RequestException
|
|
7
7
|
|
|
8
8
|
T = TypeVar('T', bound=BaseModel)
|
|
9
9
|
|
|
10
|
-
class SpireClient():
|
|
11
|
-
|
|
12
|
-
def __init__(self, company, username, password,):
|
|
10
|
+
class SpireClient():
|
|
11
|
+
"""A lightweight client to interact with the Spire API using requests sessions for connection reuse and authenticated calls."""
|
|
12
|
+
def __init__(self, host, company, username, password,):
|
|
13
|
+
"""
|
|
14
|
+
:param host (str): Spire Server host
|
|
15
|
+
:param company (str): Spire company
|
|
16
|
+
:param username (str): Spire user username.
|
|
17
|
+
:param password (str): Spire user password.
|
|
18
|
+
|
|
19
|
+
"""
|
|
13
20
|
self.session = requests.Session()
|
|
14
21
|
self.session.auth = (username, password)
|
|
15
22
|
self.session.headers.update({
|
|
16
23
|
"accept": "application/json",
|
|
17
24
|
"content-type": "application/json"
|
|
18
25
|
})
|
|
19
|
-
self.base_url = f"{
|
|
26
|
+
self.base_url = f"https://{host}/api/v2/companies/{company}"
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
response = self.session.get(self.base_url)
|
|
30
|
+
if response.text == 'No such company intertes':
|
|
31
|
+
raise ValueError(f"No company entries for {company}")
|
|
32
|
+
if response.text == 'Unauthorized':
|
|
33
|
+
raise ValueError(f"Invalid Authorization")
|
|
34
|
+
|
|
35
|
+
except ConnectionError as conn_err:
|
|
36
|
+
print(f"Connection error occurred for : {conn_err}")
|
|
37
|
+
|
|
38
|
+
except Timeout as timeout_err:
|
|
39
|
+
print(f"Request timed out: {timeout_err}")
|
|
40
|
+
|
|
41
|
+
except RequestException as req_err:
|
|
42
|
+
print(f"General error occurred: {req_err}")
|
|
43
|
+
|
|
20
44
|
|
|
21
45
|
def _get(self, endpoint, params=None):
|
|
22
46
|
|
|
@@ -45,20 +69,50 @@ class SpireClient():
|
|
|
45
69
|
return response.json()
|
|
46
70
|
|
|
47
71
|
def _post(self, endpoint, data=None, json=None):
|
|
72
|
+
"""
|
|
73
|
+
Send a POST request to the Spire API.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
endpoint (str): The relative API endpoint (e.g., 'sales/orders').
|
|
77
|
+
data (dict, optional): Data to send in the body of the request.
|
|
78
|
+
json (dict, optional): JSON data to send in the body of the request.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
dict: A dictionary containing the response status code, URL, content, and headers.
|
|
82
|
+
"""
|
|
48
83
|
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
49
84
|
response = self.session.post(url, data=data, json=json)
|
|
50
85
|
return self._handle_response(response)
|
|
51
86
|
|
|
52
87
|
def _put(self, endpoint, data=None, json=None):
|
|
88
|
+
"""
|
|
89
|
+
Send a PUT request to the Spire API.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
endpoint (str): The relative API endpoint (e.g., 'inventory/items/123').
|
|
93
|
+
data (dict, optional): Data to send in the body of the request.
|
|
94
|
+
json (dict, optional): JSON data to send in the body of the request.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
dict: A dictionary containing the response status code, URL, content, and headers.
|
|
98
|
+
"""
|
|
53
99
|
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
54
100
|
response = self.session.put(url, data=data, json=json)
|
|
55
|
-
|
|
56
|
-
return response.json()
|
|
101
|
+
return self._handle_response(response)
|
|
57
102
|
|
|
58
103
|
def _delete(self, endpoint):
|
|
104
|
+
"""
|
|
105
|
+
Send a DELETE request to the Spire API.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
endpoint (str): The relative API endpoint to delete (e.g., 'inventory/items/123').
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
bool: True if the deletion was successful (status code 200, 202, or 204), False otherwise.
|
|
112
|
+
"""
|
|
59
113
|
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
60
114
|
response = self.session.delete(url)
|
|
61
|
-
return response.status_code
|
|
115
|
+
return response.status_code in (200, 202, 204)
|
|
62
116
|
|
|
63
117
|
def _handle_response(self, response):
|
|
64
118
|
|
|
@@ -106,18 +160,25 @@ class SpireClient():
|
|
|
106
160
|
"""
|
|
107
161
|
collected = []
|
|
108
162
|
current_start = start
|
|
109
|
-
|
|
163
|
+
remaining = limit
|
|
110
164
|
while True:
|
|
165
|
+
|
|
166
|
+
current_limit = min(remaining, 1000)
|
|
111
167
|
# Build the query params as a list of tuples to allow repeated keys like 'sort'
|
|
112
168
|
params: List[Tuple[str, Any]] = [
|
|
113
169
|
("start", current_start),
|
|
114
|
-
("limit",
|
|
170
|
+
("limit", current_limit)
|
|
115
171
|
]
|
|
116
172
|
|
|
117
173
|
if query:
|
|
118
174
|
params.append(("q", query))
|
|
119
|
-
|
|
175
|
+
|
|
120
176
|
if filter:
|
|
177
|
+
model_fields = resource_cls.Model.model_fields.keys()
|
|
178
|
+
invalid_fields = [key for key in filter.keys() if key not in model_fields]
|
|
179
|
+
if invalid_fields:
|
|
180
|
+
raise ValueError(f"Invalid filter field(s): {invalid_fields}. for {resource_cls.Model.__name__} ")
|
|
181
|
+
|
|
121
182
|
encoded_filter = json.dumps(filter)
|
|
122
183
|
params.append(("filter", encoded_filter))
|
|
123
184
|
|
|
@@ -137,10 +198,19 @@ class SpireClient():
|
|
|
137
198
|
for item in items:
|
|
138
199
|
collected.append(resource_cls.from_json(item, self))
|
|
139
200
|
|
|
140
|
-
|
|
201
|
+
# Exit if:
|
|
202
|
+
# - 'all' is False and we reached the requested 'limit'
|
|
203
|
+
# - no more items are returned
|
|
204
|
+
if not all:
|
|
205
|
+
remaining -= len(items)
|
|
206
|
+
if remaining <= 0 or len(items) == 0:
|
|
207
|
+
break
|
|
208
|
+
|
|
209
|
+
# Exit if there are no more items available
|
|
210
|
+
if (current_start + current_limit) >= count or len(items) == 0:
|
|
141
211
|
break
|
|
142
212
|
|
|
143
|
-
current_start +=
|
|
213
|
+
current_start += current_limit
|
|
144
214
|
|
|
145
215
|
return collected
|
|
146
216
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from .client import APIResource, SpireClient
|
|
2
|
+
from .Models.shared_models import Note
|
|
3
|
+
|
|
4
|
+
class CRMClient():
|
|
5
|
+
|
|
6
|
+
def __init__(self, client: SpireClient):
|
|
7
|
+
self.client = client
|
|
8
|
+
self.endpoint = "crm"
|
|
9
|
+
|
|
10
|
+
def get_note(self, id : int) -> "note":
|
|
11
|
+
"""
|
|
12
|
+
Retrieve a note by its ID.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
id (int): The ID of the note to retrieve.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
note: The retrieved note
|
|
19
|
+
"""
|
|
20
|
+
response = self.client._get(f"/{self.endpoint}/notes/{str(id)}")
|
|
21
|
+
return note.from_json(response, self.client)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class note(APIResource[Note]):
|
|
25
|
+
endpoint = 'crm/notes'
|
|
26
|
+
Model = Note
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from .client import APIResource
|
|
2
2
|
from .Models.sales_models import SalesOrder, SalesOrderItem, Invoice
|
|
3
|
+
from .Models.shared_models import Note
|
|
3
4
|
from .utils import *
|
|
4
5
|
from .client import SpireClient
|
|
5
6
|
from urllib.parse import urlparse
|
|
6
7
|
from .Exceptions import CreateRequestError
|
|
7
8
|
from typing import Any, Optional, List, Dict
|
|
9
|
+
from .crm import note, CRMClient
|
|
8
10
|
|
|
9
11
|
class OrdersClient():
|
|
10
12
|
|
|
@@ -37,7 +39,7 @@ class OrdersClient():
|
|
|
37
39
|
Sends a POST request to the sales order endpoint .
|
|
38
40
|
|
|
39
41
|
Args:
|
|
40
|
-
sales_order (dict): A SalesOrder
|
|
42
|
+
sales_order (dict): A SalesOrder instance containing the sales order details.
|
|
41
43
|
|
|
42
44
|
Returns:
|
|
43
45
|
salesOrder: The created SalesOrder instance.
|
|
@@ -129,6 +131,31 @@ class OrdersClient():
|
|
|
129
131
|
**extra_params
|
|
130
132
|
)
|
|
131
133
|
|
|
134
|
+
def create_sales_order_note(self, id: int , note_body : str, note_subject : str = "Note") -> note:
|
|
135
|
+
"""
|
|
136
|
+
Create a new note on this sales order.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
id (int): The id of the salesOrder to create a note on.
|
|
140
|
+
note (str): The body of the note
|
|
141
|
+
Returns:
|
|
142
|
+
note: The created note
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
CreateRequestError: If the creation fails or response is invalid.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
note_model = Note(body=note_body, subject=note_subject)
|
|
149
|
+
response = self.client._post(f"/{self.endpoint}/{id}/notes/", json=note_model.model_dump(exclude_unset=True, exclude_none=True))
|
|
150
|
+
if response.get('status_code') == 201:
|
|
151
|
+
location = response.get('headers').get('location')
|
|
152
|
+
parsed_url = urlparse(location)
|
|
153
|
+
path_segments = parsed_url.path.rstrip("/").split("/")
|
|
154
|
+
id = path_segments[-1]
|
|
155
|
+
return CRMClient(client=self.client).get_note(id)
|
|
156
|
+
else:
|
|
157
|
+
error_message = response.get('content')
|
|
158
|
+
raise CreateRequestError(self.endpoint, status_code=response.get('status_code'), error_message=error_message)
|
|
132
159
|
|
|
133
160
|
class InvoiceClient():
|
|
134
161
|
|
|
@@ -223,12 +250,9 @@ class salesOrder(APIResource[SalesOrder]):
|
|
|
223
250
|
|
|
224
251
|
"""
|
|
225
252
|
response = self._client._post(f"/{self.endpoint}/{str(self.id)}/invoice")
|
|
226
|
-
if response.get('status_code') ==
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
path_segments = parsed_url.path.rstrip("/").split("/")
|
|
230
|
-
id = path_segments[-1]
|
|
231
|
-
return InvoiceClient(client=self._client).get_invoice(id)
|
|
253
|
+
if response.get('status_code') == 200:
|
|
254
|
+
data = response.get('content').get('invoice')
|
|
255
|
+
return salesOrder.from_json(json_data=data, client= self._client)
|
|
232
256
|
else:
|
|
233
257
|
error_message = response.get('content')
|
|
234
258
|
raise CreateRequestError(self.endpoint, status_code=response.get('status_code'), error_message=error_message)
|
|
@@ -277,6 +301,30 @@ class salesOrder(APIResource[SalesOrder]):
|
|
|
277
301
|
response = self._client._put(f"/{self.endpoint}/{str(self.id)}", json=data)
|
|
278
302
|
return salesOrder.from_json(response, self._client)
|
|
279
303
|
|
|
304
|
+
def add_note(self, note_body : "str" , note_subject : "str" = "Note") -> note:
|
|
305
|
+
"""
|
|
306
|
+
Add a note to this sales order
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
note_body (str): the body of the note.
|
|
310
|
+
note_subject (str, "Note"): the subject of the note. the defualt value is just "Note"
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
note: The note created.
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
note_model = Note(body=note_body, subject=note_subject)
|
|
317
|
+
response = self._client._post(f"/{self.endpoint}/{str(self.id)}/notes/", json=note_model.model_dump(exclude_unset=True, exclude_none=True))
|
|
318
|
+
if response.get('status_code') == 201:
|
|
319
|
+
location = response.get('headers').get('location')
|
|
320
|
+
parsed_url = urlparse(location)
|
|
321
|
+
path_segments = parsed_url.path.rstrip("/").split("/")
|
|
322
|
+
id = path_segments[-1]
|
|
323
|
+
return CRMClient(client=self._client).get_note(id)
|
|
324
|
+
else:
|
|
325
|
+
error_message = response.get('content')
|
|
326
|
+
raise CreateRequestError(f"/{self.endpoint}/{str(self.id)}/notes/", status_code=response.get('status_code'), error_message=error_message)
|
|
327
|
+
|
|
280
328
|
class invoice(APIResource[Invoice]):
|
|
281
329
|
endpoint = "sales/invoices/"
|
|
282
330
|
Model = Invoice
|
|
@@ -310,4 +358,6 @@ class invoice(APIResource[Invoice]):
|
|
|
310
358
|
"""
|
|
311
359
|
data = invoice_.model_dump(exclude_unset=True, exclude_none=True) if invoice_ else self.model_dump(exclude_unset=True, exclude_none=True)
|
|
312
360
|
response = self._client._put(f"/{self.endpoint}/{str(self.id)}", json=data)
|
|
313
|
-
return invoice.from_json(response, self._client)
|
|
361
|
+
return invoice.from_json(response, self._client)
|
|
362
|
+
|
|
363
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .client import SpireClient
|
|
2
|
+
from .sales import OrdersClient, InvoiceClient
|
|
3
|
+
from .customers import CustomerClient
|
|
4
|
+
from .inventory import InventoryClient
|
|
5
|
+
|
|
6
|
+
class Spire:
|
|
7
|
+
def __init__(self, host : str, company : str, username : str, password : str):
|
|
8
|
+
"""
|
|
9
|
+
Creates a spire Session
|
|
10
|
+
|
|
11
|
+
:param host (str): Spire Server host. eg: black-disk-5630.spirelan.com:10880
|
|
12
|
+
:param company (str): Spire company
|
|
13
|
+
:param username (str): Spire user username.
|
|
14
|
+
:param password (str): Spire user password.
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
self.client = SpireClient(host, company, username, password)
|
|
18
|
+
self.orders = OrdersClient(self.client)
|
|
19
|
+
self.invoices = InvoiceClient(self.client)
|
|
20
|
+
self.customers = CustomerClient(self.client)
|
|
21
|
+
self.inventory = InventoryClient(self.client)
|
|
22
|
+
|
|
23
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spyreapi
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: A robust and extensible Python client for interacting with the [Spire Business Software API](https://developer.spiresystems.com/reference). This client provides an object-oriented interface to get, create, update, delete, query, filter, sort, and manage various Spire modules such as Sales Orders, Invoices, Inventory Items, and more.
|
|
5
5
|
Author-email: Sanjid Sharaf <sanjidsharaf1@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -42,3 +42,38 @@ A robust and extensible Python client for interacting with the [Spire Business S
|
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
44
|
pip install -r requirements.txt
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## ⚙️ Configuration
|
|
50
|
+
|
|
51
|
+
- How to set up your spire client
|
|
52
|
+
|
|
53
|
+
### Find your Spire URL
|
|
54
|
+
|
|
55
|
+
The base URL for the Spire API is the same url provided by Spire that you use to access Spire server and uses port 10880 as default:
|
|
56
|
+
Replace {spire-url} with the url provided by Spire.
|
|
57
|
+
|
|
58
|
+
- https://{spire-url}:10880/api/v2/
|
|
59
|
+
|
|
60
|
+
Spire Cloud
|
|
61
|
+
If you are using Spire cloud you do not need to specify a port. The base URL for API for Spire Cloud customers would be:
|
|
62
|
+
|
|
63
|
+
- https://{spire-cloud-url}/api/v2/
|
|
64
|
+
|
|
65
|
+
### Set up your client with your credentials
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from spyre import Spire
|
|
69
|
+
# host is your spire url and the port if applicable
|
|
70
|
+
client = Spire(host = 'your-spire-host', company = 'comapany-name' , username = 'username' , password = 'password' )
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Example : Updating the status of an inventory item
|
|
75
|
+
```python
|
|
76
|
+
item = client.inventory.items.get_item(1101) # Gets item with id 1101
|
|
77
|
+
item.status = 1 # Use either item. or item.model. . item.model. will bring up all attributes
|
|
78
|
+
item.update()
|
|
79
|
+
```
|
|
@@ -5,7 +5,7 @@ pyproject.toml
|
|
|
5
5
|
src/spyre/Exceptions.py
|
|
6
6
|
src/spyre/__init__.py
|
|
7
7
|
src/spyre/client.py
|
|
8
|
-
src/spyre/
|
|
8
|
+
src/spyre/crm.py
|
|
9
9
|
src/spyre/customers.py
|
|
10
10
|
src/spyre/inventory.py
|
|
11
11
|
src/spyre/sales.py
|
|
@@ -20,5 +20,4 @@ src/spyreapi.egg-info/PKG-INFO
|
|
|
20
20
|
src/spyreapi.egg-info/SOURCES.txt
|
|
21
21
|
src/spyreapi.egg-info/dependency_links.txt
|
|
22
22
|
src/spyreapi.egg-info/requires.txt
|
|
23
|
-
src/spyreapi.egg-info/top_level.txt
|
|
24
|
-
tests/testing.py
|
|
23
|
+
src/spyreapi.egg-info/top_level.txt
|
spyreapi-0.0.2/MANIFEST.in
DELETED
spyreapi-0.0.2/README.md
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# Spire API Python Client
|
|
2
|
-
|
|
3
|
-
A robust and extensible Python client for interacting with the [Spire Business Software API](https://developer.spiresystems.com/reference). This client provides an object-oriented interface to get, create, update, delete, query, filter, sort, and manage various Spire modules such as Sales Orders, Invoices, Inventory Items, and more.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## ✨ Features
|
|
8
|
-
|
|
9
|
-
- ✅ Object-oriented resource wrappers for each module (e.g., `salesOrder`, `invoice`, `item`)
|
|
10
|
-
- 🔍 Full-text search via `q` parameter
|
|
11
|
-
- 🔁 Pagination with `start` and `limit` support
|
|
12
|
-
- 🧾 JSON-based advanced filtering (supports `$gt`, `$lt`, `$in`, `$or`, etc.)
|
|
13
|
-
- ↕️ Multi-field sorting with ascending/descending control
|
|
14
|
-
- 🔧 Clean abstraction layer for API endpoints
|
|
15
|
-
- 📦 Powered by `pydantic` models for validation
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## 📦 Installation
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
pip install -r requirements.txt
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
from .client import SpireClient
|
|
2
|
-
from .sales import OrdersClient, InvoiceClient
|
|
3
|
-
from .customers import CustomerClient
|
|
4
|
-
from .inventory import InventoryClient
|
|
5
|
-
|
|
6
|
-
class Spire:
|
|
7
|
-
def __init__(self, company : str, username : str, password : str):
|
|
8
|
-
self.client = SpireClient(company, username, password)
|
|
9
|
-
self.orders = OrdersClient(self.client)
|
|
10
|
-
self.invoices = InvoiceClient(self.client)
|
|
11
|
-
self.customers = CustomerClient(self.client)
|
|
12
|
-
self.inventory = InventoryClient(self.client)
|
|
13
|
-
|
|
14
|
-
|
spyreapi-0.0.2/tests/testing.py
DELETED
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
import spyre
|
|
2
|
-
import spyre.spire
|
|
3
|
-
|
|
4
|
-
"""
|
|
5
|
-
Tested
|
|
6
|
-
> Get Order
|
|
7
|
-
> Pydantic Model
|
|
8
|
-
> Creating Order
|
|
9
|
-
> Deleting Order
|
|
10
|
-
> Updating Order
|
|
11
|
-
> Processing a order
|
|
12
|
-
> Invoicing a order
|
|
13
|
-
> Reversing a invoice(Check with eric)
|
|
14
|
-
> Get Invoice
|
|
15
|
-
> Update Invoice
|
|
16
|
-
> Get Customer
|
|
17
|
-
> Create Customer
|
|
18
|
-
> Update Customer
|
|
19
|
-
> Delete Customer
|
|
20
|
-
> Querying
|
|
21
|
-
> Filtering
|
|
22
|
-
> Sorting
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Todo
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
>Exceptions (Duplicate , Processing quotes, Invalid inPuts)
|
|
29
|
-
>Documentation
|
|
30
|
-
>Response handling/ object -> Replace Riase_for_status
|
|
31
|
-
>Invoice Items/ Sales Order Item
|
|
32
|
-
>Payments, Deposit on Sales Order
|
|
33
|
-
>get by orderNo/invoiceNo etc
|
|
34
|
-
|
|
35
|
-
# >Querying
|
|
36
|
-
# >filtering and sorting
|
|
37
|
-
# >Create returns the item created
|
|
38
|
-
# >Customer
|
|
39
|
-
# >Make Wrapper class and implemement Oob type classes & methods
|
|
40
|
-
# >Create a model for Orders
|
|
41
|
-
# >Test Create,Update,
|
|
42
|
-
# >Processing Orders
|
|
43
|
-
# >Cancel/Delete
|
|
44
|
-
# >Return
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
spire = Spire('intertest' , 'david1' , 'david')
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
order = spire.orders.get_sales_order(51247)
|
|
54
|
-
order.model.location = "01"
|
|
55
|
-
print(order.update())
|
|
56
|
-
|
|
57
|
-
# order = spire.orders.get_sales_order(51245)
|
|
58
|
-
# print(order.invoice())
|
|
59
|
-
|
|
60
|
-
TAXES = {
|
|
61
|
-
0.05 : 1,
|
|
62
|
-
|
|
63
|
-
0.08 : 2,
|
|
64
|
-
0.13 : 3,
|
|
65
|
-
0.15 : 4,
|
|
66
|
-
0.14 : 5,
|
|
67
|
-
0.12 : 6,
|
|
68
|
-
0.11 : 7
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
order = {
|
|
72
|
-
"amazon-order-id": "702-6212155-2707463",
|
|
73
|
-
"merchant-order-id": "3045",
|
|
74
|
-
"purchase-date": "5/31/2025 23:22",
|
|
75
|
-
"last-updated-date": "6/2/2025 11:08",
|
|
76
|
-
"order-status": "Shipped",
|
|
77
|
-
"fulfillment-channel": "Merchant",
|
|
78
|
-
"sales-channel": "Amazon.ca",
|
|
79
|
-
"order-channel": "WebsiteOrderChannel",
|
|
80
|
-
"url": "",
|
|
81
|
-
"ship-service-level": "Standard",
|
|
82
|
-
"product-name": "20V MAX 1/2IN Compact Impact WR- HR",
|
|
83
|
-
"sku": "DEWDCF921B",
|
|
84
|
-
"asin": "B09M3TL9BB",
|
|
85
|
-
"number-of-items": 1,
|
|
86
|
-
"item-status": "Shipped",
|
|
87
|
-
"tax-collection-model": "",
|
|
88
|
-
"tax-collection-responsible-party": "",
|
|
89
|
-
"quantity": 1,
|
|
90
|
-
"currency": "CAD",
|
|
91
|
-
"item-price": 195,
|
|
92
|
-
"item-tax": 21.45,
|
|
93
|
-
"shipping-price": "",
|
|
94
|
-
"shipping-tax": "",
|
|
95
|
-
"gift-wrap-price": "",
|
|
96
|
-
"gift-wrap-tax": "",
|
|
97
|
-
"item-promotion-discount": "",
|
|
98
|
-
"ship-promotion-discount": "",
|
|
99
|
-
"address-type": "",
|
|
100
|
-
"ship-city": "Maple Creek",
|
|
101
|
-
"ship-state": "Saskatchewan",
|
|
102
|
-
"ship-postal-code": "S0N 1N0",
|
|
103
|
-
"ship-country": "CA",
|
|
104
|
-
"promotion-ids": "",
|
|
105
|
-
"is-business-order": False,
|
|
106
|
-
"purchase-order-number": "",
|
|
107
|
-
"price-designation": "",
|
|
108
|
-
"fulfilled-by": "",
|
|
109
|
-
"default-ship-from-address-name": "Interline Wholesale Hardware Distributors North",
|
|
110
|
-
"default-ship-from-address-field-1": "399 Confederation Parkway",
|
|
111
|
-
"default-ship-from-address-field-2": "",
|
|
112
|
-
"default-ship-from-address-field-3": "",
|
|
113
|
-
"default-ship-from-city": "Vaughan",
|
|
114
|
-
"default-ship-from-state": "Ontario",
|
|
115
|
-
"default-ship-from-country": "CA",
|
|
116
|
-
"default-ship-from-postal-code": "L4K 4S1",
|
|
117
|
-
"actual-ship-from-address-name": "Interline Wholesale Hardware Distributors North",
|
|
118
|
-
"actual-ship-from-address-field-1": "399 Confederation Parkway",
|
|
119
|
-
"actual-ship-from-address-field-2": "",
|
|
120
|
-
"actual-ship-from-address-field-3": "",
|
|
121
|
-
"actual-ship-from-city": "Vaughan",
|
|
122
|
-
"actual-ship-from-state": "Ontario",
|
|
123
|
-
"actual-ship-from-country": "CA",
|
|
124
|
-
"actual-ship-from-postal-code": "L4K 4S1",
|
|
125
|
-
"serial-numbers": ""
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
from datetime import datetime
|
|
129
|
-
|
|
130
|
-
def convert_order_date(date_str: str) -> str:
|
|
131
|
-
try:
|
|
132
|
-
dt = datetime.strptime(date_str, "%m/%d/%Y %H:%M")
|
|
133
|
-
return dt.strftime("%Y-%m-%d")
|
|
134
|
-
except ValueError:
|
|
135
|
-
return ""
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def get_tax_code(order: dict) -> int:
|
|
139
|
-
try:
|
|
140
|
-
item_price = float(order.get('item-price', 0))
|
|
141
|
-
item_tax = float(order.get('item-tax', 0))
|
|
142
|
-
|
|
143
|
-
if item_price == 0:
|
|
144
|
-
raise ValueError("Item price cannot be zero.")
|
|
145
|
-
|
|
146
|
-
tax_rate = item_tax / item_price
|
|
147
|
-
print(tax_rate)
|
|
148
|
-
# Find the closest tax rate key
|
|
149
|
-
closest_rate = min(TAXES.keys(), key=lambda r: abs(r - tax_rate))
|
|
150
|
-
print(closest_rate)
|
|
151
|
-
return TAXES[closest_rate]
|
|
152
|
-
|
|
153
|
-
except (TypeError, ValueError):
|
|
154
|
-
return None # or raise an error if preferred
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def convert_order_to_sales_order(order: dict) -> SalesOrder:
|
|
158
|
-
|
|
159
|
-
amazn_id = order.get('amazon-order-id')
|
|
160
|
-
udf = {
|
|
161
|
-
"shopid" : amazn_id,
|
|
162
|
-
"shipped" : order.get('order-status') == 'shipped'
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
status = "O"
|
|
166
|
-
# if order.get('order-status') == 'shipped':
|
|
167
|
-
# status = "H"
|
|
168
|
-
|
|
169
|
-
tax_code = get_tax_code(order)
|
|
170
|
-
|
|
171
|
-
shipping_address = Address(city=order.get('ship-city'), provState= order.get('ship-state'), postalCode = order.get('ship-postal-code'), country=order.get('ship-country'),
|
|
172
|
-
salesTaxes=[ { "code" : tax_code } ]
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
items = []
|
|
176
|
-
item = SalesOrderItem(
|
|
177
|
-
inventory=Inventory(partNo=order.get("sku"), whse='00'),
|
|
178
|
-
partNo=order.get("sku"),
|
|
179
|
-
orderQty=str(order.get("quantity")),
|
|
180
|
-
unitPrice=str( float(order.get("item-price")) /float(order.get("quantity")) ),
|
|
181
|
-
taxFlags= [True,True,True,True],
|
|
182
|
-
committedQty=str(order.get("quantity")),
|
|
183
|
-
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
items.append(item)
|
|
187
|
-
freight = str(
|
|
188
|
-
float(order.get('shipping-tax', '0') or 0) -
|
|
189
|
-
float(order.get('ship-promotion-discount', '0') or 0)
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
customer = Customer(customerNo="AMAZON")
|
|
193
|
-
|
|
194
|
-
return SalesOrder(
|
|
195
|
-
orderNo= "AMZN3" + amazn_id[-5:] ,
|
|
196
|
-
orderDate=convert_order_date(order.get("purchase-date")),
|
|
197
|
-
type = "O",
|
|
198
|
-
referenceNo= amazn_id,
|
|
199
|
-
status=status,
|
|
200
|
-
currency=Currency(code=order.get("currency")) if order.get("currency") else None,
|
|
201
|
-
shippingAddress=shipping_address,
|
|
202
|
-
items=items,
|
|
203
|
-
udf=udf,
|
|
204
|
-
freight=freight,
|
|
205
|
-
customer=customer,
|
|
206
|
-
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
sales_order = convert_order_to_sales_order(order)
|
|
210
|
-
print(sales_order.model_dump_json(indent=2 , exclude_none= True))
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
# order.model.payments = [ {"method" : "06" , "amount" : "216.45" , "layawayFlag" : False}]
|
|
215
|
-
# order.update()
|
|
216
|
-
# print(order.invoice())
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
# uoms = spire.inventory.items.get_item_uoms(id = 811)
|
|
221
|
-
# test_uom = uoms[2]
|
|
222
|
-
# print(test_uom.description)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# inv = spire.invoices.get_invoice(206363)
|
|
226
|
-
# order_converted.
|
|
227
|
-
# order_converted = create_sales_order_from_invoice(inv.model)
|
|
228
|
-
# print(order_converted.model_dump_json(exclude_none=True, exclude_unset=True, indent=2))
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
# cust = spire.customers.get_customer(1)
|
|
232
|
-
|
|
233
|
-
# ord = spire.orders.get_sales_order(31319)
|
|
234
|
-
# ord.customer = cust.model
|
|
235
|
-
|
|
236
|
-
# print(ord.update())
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
# order_test = json.loads("""
|
|
240
|
-
|
|
241
|
-
# {
|
|
242
|
-
# "customer": {
|
|
243
|
-
# "id" : 3143
|
|
244
|
-
# },
|
|
245
|
-
# "currency": {
|
|
246
|
-
# "code": "CAD"
|
|
247
|
-
|
|
248
|
-
# },
|
|
249
|
-
# "address": {
|
|
250
|
-
# "country": "Can",
|
|
251
|
-
# "defaultWarehouse": "00",
|
|
252
|
-
# "line1": "629 Daintry Crescent",
|
|
253
|
-
# "line2": "Cobourg ON K9A 4X9",
|
|
254
|
-
# "email": "beatty731@yahoo.com",
|
|
255
|
-
# "contacts": [
|
|
256
|
-
# {
|
|
257
|
-
# "phone": {
|
|
258
|
-
# "number": "+1 905-207-1116"
|
|
259
|
-
# }
|
|
260
|
-
# }
|
|
261
|
-
# ]
|
|
262
|
-
# },
|
|
263
|
-
# "contact": {
|
|
264
|
-
# "phone": {
|
|
265
|
-
# "number": "+1 905-207-1116"
|
|
266
|
-
# },
|
|
267
|
-
# "name": "safdasd"
|
|
268
|
-
# },
|
|
269
|
-
# "shippingCarrier": "1",
|
|
270
|
-
# "referenceNo": "1",
|
|
271
|
-
# "trackingNo": "1",
|
|
272
|
-
# "shipDate": null,
|
|
273
|
-
# "items": [
|
|
274
|
-
# {
|
|
275
|
-
# "inventory": {
|
|
276
|
-
# "id": 12345,
|
|
277
|
-
# "whse": "00"
|
|
278
|
-
# }
|
|
279
|
-
# }
|
|
280
|
-
# ],
|
|
281
|
-
# "freight": "1",
|
|
282
|
-
# "discount": "1",
|
|
283
|
-
# "surcharge": "1",
|
|
284
|
-
# "status": "O",
|
|
285
|
-
# "type": "Q",
|
|
286
|
-
# "hold": false,
|
|
287
|
-
# "customerPO": "1"
|
|
288
|
-
# }
|
|
289
|
-
|
|
290
|
-
# """)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
# order = SalesOrder(**order_test)
|
|
295
|
-
# print(order.model_dump(exclude_unset=True, ))
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
# spire.orders.get_sales_order
|
|
300
|
-
# invoice_test = spire.invoices.get_invoice(206362)
|
|
301
|
-
# response = invoice_test.reverse()
|
|
302
|
-
# print(response)
|
|
303
|
-
|
|
304
|
-
# order = spire.orders.get_sales_order(31312)
|
|
305
|
-
# response = order.invoice()
|
|
306
|
-
|
|
307
|
-
# response = order.update_sales_order
|
|
308
|
-
|
|
309
|
-
# order = spire.orders.get_sales_order(31307)
|
|
310
|
-
# response = order.delete()
|
|
311
|
-
# print(response)
|
|
312
|
-
|
|
313
|
-
# resp = spire.orders.delete_sales_order(31306)
|
|
314
|
-
# print(resp)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
# response = spire.orders.create_sales_order(order_test)
|
|
319
|
-
# print(response)
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
# orders_client = OrdersClient(client)
|
|
324
|
-
# order = orders_client.get_order(12334)
|
|
325
|
-
# order.invoice()
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
# invoice_client = InvoiceClient(client)
|
|
329
|
-
|
|
330
|
-
# response = invoice_client.reverse_invoice(206354)
|
|
331
|
-
# print(response)
|
|
332
|
-
|
|
333
|
-
# invoice_em = invoice_client.get_invoice(206355)
|
|
334
|
-
# invoice_model = Invoice(**invoice_em)
|
|
335
|
-
|
|
336
|
-
# print(invoice_model.model_dump_json(indent=2))
|
|
337
|
-
|
|
338
|
-
# order_converted = create_sales_order_from_invoice(invoice_model)
|
|
339
|
-
# print(order_converted.model_dump_json(indent=2, exclude_unset=True, exclude_none=True))
|
|
340
|
-
|
|
341
|
-
# response = orders_client.create_order(order_converted.model_dump(exclude_unset=True, exclude_none=True))
|
|
342
|
-
# print(response)
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
# Get an order by ID
|
|
346
|
-
# order_1001 = orders_client.get_order(1001)
|
|
347
|
-
# try:
|
|
348
|
-
# orderModel = SalesOrder(**order_1001)
|
|
349
|
-
# except Exception as e:
|
|
350
|
-
# print(e.errors())
|
|
351
|
-
|
|
352
|
-
# print(order)
|
|
353
|
-
# print(orderModel.model_dump_json(indent=2))
|
|
354
|
-
|
|
355
|
-
#Create an ORder
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
# response = orders_client.create_order(order_test)
|
|
360
|
-
# print(response)
|
|
361
|
-
|
|
362
|
-
# deleting An order
|
|
363
|
-
# response = orders_client.delete_order(31299)
|
|
364
|
-
# print(response)
|
|
365
|
-
|
|
366
|
-
# Updating an order
|
|
367
|
-
# orderModel.location = "01"
|
|
368
|
-
# print(orderModel.model_dump_json(indent=2))
|
|
369
|
-
|
|
370
|
-
# response = orders_client.update_order(1001, orderModel.model_dump_json())
|
|
371
|
-
# print(response)
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
# order_test_model = SalesOrder(**order_test)
|
|
375
|
-
# print(order_test_model.model_dump(exclude_none=True))
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|