spyreapi 0.0.1__py3-none-any.whl
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.
- spyre/Exceptions.py +45 -0
- spyre/Models/__init__.py +0 -0
- spyre/Models/customers_models.py +40 -0
- spyre/Models/inventory_models.py +160 -0
- spyre/Models/sales_models.py +170 -0
- spyre/Models/shared_models.py +96 -0
- spyre/__init__.py +22 -0
- spyre/client.py +208 -0
- spyre/config.py +6 -0
- spyre/customers.py +157 -0
- spyre/inventory.py +344 -0
- spyre/sales.py +313 -0
- spyre/spire.py +14 -0
- spyre/utils.py +130 -0
- spyreapi-0.0.1.dist-info/METADATA +37 -0
- spyreapi-0.0.1.dist-info/RECORD +19 -0
- spyreapi-0.0.1.dist-info/WHEEL +5 -0
- spyreapi-0.0.1.dist-info/licenses/LICENSE +21 -0
- spyreapi-0.0.1.dist-info/top_level.txt +1 -0
spyre/inventory.py
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
from Models.inventory_models import *
|
|
2
|
+
import utils
|
|
3
|
+
from client import *
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
from Exceptions import *
|
|
6
|
+
from typing import Any
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
""""
|
|
10
|
+
TODO Get Upc
|
|
11
|
+
Create UPC
|
|
12
|
+
Query UPC
|
|
13
|
+
Delete UPC
|
|
14
|
+
Update UPC
|
|
15
|
+
|
|
16
|
+
Sell Prices
|
|
17
|
+
|
|
18
|
+
Counts
|
|
19
|
+
Adjustements
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
class InventoryClient():
|
|
24
|
+
|
|
25
|
+
def __init__(self, client : SpireClient):
|
|
26
|
+
self.client = client
|
|
27
|
+
self.endpoint = 'inventory'
|
|
28
|
+
self.items = ItemsClient(client=client)
|
|
29
|
+
self.upcs = UpcClient(client=client)
|
|
30
|
+
|
|
31
|
+
def __getattr__(self, name):
|
|
32
|
+
|
|
33
|
+
if hasattr(self.items, name):
|
|
34
|
+
return getattr(self.items, name)
|
|
35
|
+
|
|
36
|
+
if hasattr(self.upcs, name):
|
|
37
|
+
return getattr(self.upcs, name)
|
|
38
|
+
raise AttributeError(f"'InventoryClient' object has no attribute '{name}'")
|
|
39
|
+
|
|
40
|
+
class ItemsClient():
|
|
41
|
+
|
|
42
|
+
def __init__(self, client : SpireClient):
|
|
43
|
+
self.client = client
|
|
44
|
+
self.endpoint = 'inventory/items'
|
|
45
|
+
|
|
46
|
+
def get_item(self, id) -> "item":
|
|
47
|
+
"""
|
|
48
|
+
Retrieve a inventory item by its ID.
|
|
49
|
+
|
|
50
|
+
Sends a GET request to the Spire API to fetch a inentory item data for the
|
|
51
|
+
specified ID. Wraps the result in a `item` instance, which
|
|
52
|
+
retains a reference to the client for further actions.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
id (int): The ID of the sales order to retrieve.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
salesOrder: A `item` wrapper instance containing the retrieved
|
|
59
|
+
data and a reference to the client session.
|
|
60
|
+
"""
|
|
61
|
+
response = self.client._get(f"/{self.endpoint}/{str(id)}")
|
|
62
|
+
return item.from_json(response, self.client)
|
|
63
|
+
|
|
64
|
+
def create_item(self, item : 'InventoryItem') -> 'item':
|
|
65
|
+
"""
|
|
66
|
+
Create a new Inventory Item in SPire.
|
|
67
|
+
|
|
68
|
+
Sends a POST request to the Inventory/Items endpoint .
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
item (dict): A InventoryItem instace containing the sales order details.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
item: The created InventoryItem instance.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
CreateRequestError: If the creation fails or response is invalid.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
response = self.client._post(f"/{self.endpoint}", json=item.model_dump(exclude_unset=True, exclude_none=True))
|
|
81
|
+
if response.get('status_code') == 201:
|
|
82
|
+
location = response.get('headers').get('location')
|
|
83
|
+
parsed_url = urlparse(location)
|
|
84
|
+
path_segments = parsed_url.path.rstrip("/").split("/")
|
|
85
|
+
id = path_segments[-1]
|
|
86
|
+
return self.get_item(id)
|
|
87
|
+
else:
|
|
88
|
+
error_message = response.get('content')
|
|
89
|
+
raise CreateRequestError(self.endpoint, status_code=response.get('status_code'), error_message=error_message)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def update_item(self, id: int, inventory_item : 'InventoryItem') -> 'item':
|
|
93
|
+
"""
|
|
94
|
+
Update an existing Inventory Item by ID.
|
|
95
|
+
|
|
96
|
+
Sends a PUT request to the Inventory/Items endpoint with the provided data
|
|
97
|
+
to update the existing record. Returns a wrapped `item` object containing
|
|
98
|
+
the updated information.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
id (int): The ID of the item to update.
|
|
102
|
+
item (InventoryItem): A InventoryItem instance with the sales order details.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
item: An instance of the item wrapper class initialized with
|
|
106
|
+
the updated data and client session.
|
|
107
|
+
"""
|
|
108
|
+
response = self.client._put(f"/{self.endpoint}/{str(id)}", json=inventory_item.model_dump(exclude_none=True, exclude_unset=True))
|
|
109
|
+
return item.from_json(response, self.client)
|
|
110
|
+
|
|
111
|
+
def delete_item(self, id: int) -> bool:
|
|
112
|
+
"""
|
|
113
|
+
Delete a inventory_item by its ID.
|
|
114
|
+
|
|
115
|
+
Sends a DELETE request to the endpoint to remove the specified
|
|
116
|
+
inventory item from the system.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
id (int): The ID of the inventory item to delete.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
bool: True if the item was successfully deleted, False otherwise.
|
|
123
|
+
"""
|
|
124
|
+
return self.client._delete(f"/{self.endpoint}/{str(id)}")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def query_inventory_items(
|
|
128
|
+
self,
|
|
129
|
+
*,
|
|
130
|
+
query: Optional[str] = None,
|
|
131
|
+
sort: Optional[Dict[str, str]] = None,
|
|
132
|
+
filter: Optional[Dict[str, Any]] = None,
|
|
133
|
+
all: bool = False,
|
|
134
|
+
limit: int = 1000,
|
|
135
|
+
start: int = 0,
|
|
136
|
+
**extra_params
|
|
137
|
+
) -> List["item"]:
|
|
138
|
+
"""
|
|
139
|
+
Query inventory items with optional full-text search, filtering, multi-field sorting, and pagination.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
q (str, optional): Full-text search string.
|
|
143
|
+
sort (dict, optional): Dictionary of sorting rules (e.g., {"orderDate": "desc", "orderNo": "asc"}).
|
|
144
|
+
filter (dict, optional): Dictionary of filters to apply (will be JSON-encoded and URL-safe).
|
|
145
|
+
all (bool, optional): If True, retrieves all pages of results.
|
|
146
|
+
limit (int, optional): Number of results per page (max 1000).
|
|
147
|
+
start (int, optional): Starting offset for pagination.
|
|
148
|
+
**extra_params: Any additional parameters to include in the query.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
List[item]: List of wrapped sales order resources.
|
|
152
|
+
"""
|
|
153
|
+
return self.client._query(
|
|
154
|
+
endpoint=self.endpoint,
|
|
155
|
+
resource_cls=item,
|
|
156
|
+
query=query,
|
|
157
|
+
sort=sort,
|
|
158
|
+
filter=filter,
|
|
159
|
+
all=all,
|
|
160
|
+
limit=limit,
|
|
161
|
+
start=start,
|
|
162
|
+
**extra_params
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def get_item_uoms(self, id : int) -> List["uom"]:
|
|
166
|
+
|
|
167
|
+
uoms = []
|
|
168
|
+
response = self.client._get(f"{self.endpoint}/{str(id)}/uoms")
|
|
169
|
+
items = response.get('records')
|
|
170
|
+
for item in items:
|
|
171
|
+
uoms.append(uom.from_json(json_data=item, client = self.client, item_id = id))
|
|
172
|
+
|
|
173
|
+
return uoms
|
|
174
|
+
|
|
175
|
+
def get_uom(self, item_id :int , uom_id : int) -> "uom":
|
|
176
|
+
|
|
177
|
+
response = self.client._get(f"/{self.endpoint}/{str(item_id)}/uoms/{str(uom_id)}")
|
|
178
|
+
return uom.from_json(response, self.client)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def create_item_uom(self, id: int, uom : UnitOfMeasure) -> List["uom"]:
|
|
182
|
+
|
|
183
|
+
response = self.client._post(f"/{self.endpoint}/{str(id)}/uoms", json=uom.model_dump(exclude_unset=True, exclude_none=True))
|
|
184
|
+
if response.get('status_code') == 201:
|
|
185
|
+
location = response.get('headers').get('location')
|
|
186
|
+
parsed_url = urlparse(location)
|
|
187
|
+
path_segments = parsed_url.path.rstrip("/").split("/")
|
|
188
|
+
id = path_segments[-1]
|
|
189
|
+
return self.get_uom(id)
|
|
190
|
+
else:
|
|
191
|
+
error_message = response.get('content')
|
|
192
|
+
raise CreateRequestError(self.endpoint, status_code=response.get('status_code'), error_message=error_message)
|
|
193
|
+
|
|
194
|
+
def delete_uom(self, item_id : int, uom_id :int) -> bool:
|
|
195
|
+
|
|
196
|
+
return self.client._delete(f"/{self.endpoint}/{str(item_id)}/uoms/{str(uom_id)}")
|
|
197
|
+
|
|
198
|
+
def update_item_uom(self, item_id: int, uom_id : int, uom :'uom') -> 'uom':
|
|
199
|
+
|
|
200
|
+
response = self.client._put(f"/{self.endpoint}/{str(item_id)}/{str(uom_id)}", json=uom.model_dump(exclude_none=True, exclude_unset=True))
|
|
201
|
+
return uom.from_json(response, self.client)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def get_item_upcs(self, id : int) -> List["upc"]:
|
|
205
|
+
|
|
206
|
+
upcs = []
|
|
207
|
+
response = self.client._get(f"{self.endpoint}/{str(id)}/upcs")
|
|
208
|
+
items = response.get('records')
|
|
209
|
+
for item in items:
|
|
210
|
+
upcs.append(upc.from_json(json_data=item, client = self.client, item_id = id))
|
|
211
|
+
|
|
212
|
+
return upcs
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# def get_uom(self, item_id :int , uom_id : int) -> "uom":
|
|
216
|
+
|
|
217
|
+
# response = self.client._get(f"/{self.endpoint}/{str(item_id)}/uoms/{str(uom_id)}")
|
|
218
|
+
# return uom.from_json(response, self.client)
|
|
219
|
+
|
|
220
|
+
class item(APIResource[InventoryItem]):
|
|
221
|
+
|
|
222
|
+
endpoint = 'inventory/items'
|
|
223
|
+
Model = InventoryItem
|
|
224
|
+
|
|
225
|
+
"""
|
|
226
|
+
#TODO Check Upload, Add Uom & UPC, Add Price Matrix Record, Setting Sell & Buy UOM
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
def delete(self):
|
|
231
|
+
"""
|
|
232
|
+
Cancels or deletes the item.
|
|
233
|
+
|
|
234
|
+
Sends a DELETE request to the API to remove the inventory item with the current ID.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
bool: True if the order was successfully deleted (HTTP 204 or 200), False otherwise.
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
return self._client._delete(f"/{self.endpoint}/{str(self.id)}")
|
|
241
|
+
|
|
242
|
+
def update(self, inventory_item: "item" = None) -> 'item':
|
|
243
|
+
"""
|
|
244
|
+
Update the inventory item.
|
|
245
|
+
|
|
246
|
+
If no item object is provided, updates the current instance on the server.
|
|
247
|
+
If an item object is provided, updates the item using the given data.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
inventory_item (item, optional): An optional item instance to use for the update.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
item: The updated item object reflecting the new status.
|
|
254
|
+
"""
|
|
255
|
+
data = inventory_item.model_dump(exclude_unset=True, exclude_none=True) if inventory_item else self.model_dump(exclude_unset=True, exclude_none=True)
|
|
256
|
+
response = self._client._put(f"/{self.endpoint}/{str(self.id)}", json=data)
|
|
257
|
+
return item.from_json(response, self._client)
|
|
258
|
+
|
|
259
|
+
def get_uoms(self) -> List["uom"]:
|
|
260
|
+
|
|
261
|
+
uoms = []
|
|
262
|
+
response = self._client._get(f"{self.endpoint}/{self.id}/uoms")
|
|
263
|
+
items = response.get('records')
|
|
264
|
+
for item in items:
|
|
265
|
+
uoms.append(uom.from_json(json_data=item, client = self._client, item_id = self.id))
|
|
266
|
+
|
|
267
|
+
return uoms
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def add_uom(self, uom : UnitOfMeasure) -> List["uom"]:
|
|
271
|
+
|
|
272
|
+
response = self.client._post(f"/{self.endpoint}/{str(self.id)}/uoms", json=uom.model_dump(exclude_unset=True, exclude_none=True))
|
|
273
|
+
if response.get('status_code') == 201:
|
|
274
|
+
location = response.get('headers').get('location')
|
|
275
|
+
parsed_url = urlparse(location)
|
|
276
|
+
path_segments = parsed_url.path.rstrip("/").split("/")
|
|
277
|
+
id = path_segments[-1]
|
|
278
|
+
return self.get_uom(id)
|
|
279
|
+
else:
|
|
280
|
+
error_message = response.get('content')
|
|
281
|
+
raise CreateRequestError(self.endpoint, status_code=response.get('status_code'), error_message=error_message)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class uom(APIResource[UnitOfMeasure]):
|
|
285
|
+
Model = UnitOfMeasure
|
|
286
|
+
_endpoint = '' # Will be dynamically set in __init__
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def __init__(self, model, client, item_id=None):
|
|
290
|
+
if item_id is None:
|
|
291
|
+
raise ValueError("item_id must be provided")
|
|
292
|
+
super().__init__(model, client)
|
|
293
|
+
uom_id = model.id
|
|
294
|
+
self._endpoint = f'inventory/items/{item_id}/uoms/{str(uom_id)}'
|
|
295
|
+
self._item_id = item_id
|
|
296
|
+
|
|
297
|
+
def delete(self):
|
|
298
|
+
"""
|
|
299
|
+
Cancels or deletes the uom.
|
|
300
|
+
|
|
301
|
+
Sends a DELETE request to the API to remove the uom with the current ID.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
bool: True if the uom was successfully deleted (HTTP 204 or 200), False otherwise.
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
return self._client._delete(f"/{self._endpoint}")
|
|
308
|
+
|
|
309
|
+
def update(self, _uom: "uom" = None) -> 'uom':
|
|
310
|
+
"""
|
|
311
|
+
Update the uom.
|
|
312
|
+
|
|
313
|
+
If no uom object is provided, updates the current instance on the server.
|
|
314
|
+
If an uom object is provided, updates the item using the given data.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
uom (uom, optional): An optional uom instance to use for the update.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
uom: The updated uom object reflecting the new status.
|
|
321
|
+
"""
|
|
322
|
+
data = _uom.model_dump(exclude_unset=True, exclude_none=True) if _uom else self.model_dump(exclude_unset=True, exclude_none=True)
|
|
323
|
+
response = self._client._put(f"/{self._endpoint}", json=data)
|
|
324
|
+
return uom.from_json(response, self._client, item_id = self._item_id)
|
|
325
|
+
|
|
326
|
+
class UpcClient():
|
|
327
|
+
|
|
328
|
+
def __init__(self, client : SpireClient):
|
|
329
|
+
self.endpoint = 'inventory/upcs'
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class upc(APIResource[UPC]):
|
|
335
|
+
_endpoint = ''
|
|
336
|
+
Model = ''
|
|
337
|
+
|
|
338
|
+
def __init__(self, model, client, item_id=None):
|
|
339
|
+
if item_id is None:
|
|
340
|
+
raise ValueError("item_id must be provided")
|
|
341
|
+
super().__init__(model, client)
|
|
342
|
+
upc_id = model.id
|
|
343
|
+
self._endpoint = f'inventory/items/{item_id}/upcs/{str(upc_id)}'
|
|
344
|
+
self._item_id = item_id
|
spyre/sales.py
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
from client import APIResource
|
|
2
|
+
from Models.sales_models import *
|
|
3
|
+
import utils
|
|
4
|
+
from client import SpireClient
|
|
5
|
+
from urllib.parse import urlparse
|
|
6
|
+
from Exceptions import *
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
class OrdersClient():
|
|
10
|
+
|
|
11
|
+
def __init__(self, client: SpireClient):
|
|
12
|
+
self.client = client
|
|
13
|
+
self.endpoint = "sales/orders"
|
|
14
|
+
|
|
15
|
+
def get_sales_order(self, id: int, ) -> "salesOrder":
|
|
16
|
+
"""
|
|
17
|
+
Retrieve a sales order by its ID.
|
|
18
|
+
|
|
19
|
+
Sends a GET request to the Spire API to fetch sales order data for the
|
|
20
|
+
specified ID. Wraps the result in a `salesOrder` instance, which
|
|
21
|
+
retains a reference to the client for further actions.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
id (int): The ID of the sales order to retrieve.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
salesOrder: A `salesOrder` wrapper instance containing the retrieved
|
|
28
|
+
data and a reference to the client session.
|
|
29
|
+
"""
|
|
30
|
+
response = self.client._get(f"/{self.endpoint}/{str(id)}")
|
|
31
|
+
return salesOrder.from_json(response, self.client)
|
|
32
|
+
|
|
33
|
+
def create_sales_order(self, sales_order : 'SalesOrder') -> 'salesOrder':
|
|
34
|
+
"""
|
|
35
|
+
Create a new sales order.
|
|
36
|
+
|
|
37
|
+
Sends a POST request to the sales order endpoint .
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
sales_order (dict): A SalesOrder instace containing the sales order details.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
salesOrder: The created SalesOrder instance.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
CreateRequestError: If the creation fails or response is invalid.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
response = self.client._post(f"/{self.endpoint}", json=sales_order.model_dump(exclude_unset=True, exclude_none=True))
|
|
50
|
+
if response.get('status_code') == 201:
|
|
51
|
+
location = response.get('headers').get('location')
|
|
52
|
+
parsed_url = urlparse(location)
|
|
53
|
+
path_segments = parsed_url.path.rstrip("/").split("/")
|
|
54
|
+
id = path_segments[-1]
|
|
55
|
+
return self.get_sales_order(id)
|
|
56
|
+
else:
|
|
57
|
+
error_message = response.get('content')
|
|
58
|
+
raise CreateRequestError(self.endpoint, status_code=response.get('status_code'), error_message=error_message)
|
|
59
|
+
|
|
60
|
+
def update_sales_order(self, id: int, sales_order : 'SalesOrder') -> 'salesOrder':
|
|
61
|
+
"""
|
|
62
|
+
Update an existing sales order by ID.
|
|
63
|
+
|
|
64
|
+
Sends a PUT request to the sales order endpoint with the provided sales order data
|
|
65
|
+
to update the existing record. Returns a wrapped `salesOrder` object containing
|
|
66
|
+
the updated information.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
id (int): The ID of the sales order to update.
|
|
70
|
+
sales_order (SaleOrder): A SalesOrder instance with the sales order details.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
salesOrder: An instance of the salesOrder wrapper class initialized with
|
|
74
|
+
the updated data and client session.
|
|
75
|
+
"""
|
|
76
|
+
response = self.client._put(f"/{self.endpoint}/{str(id)}", json=sales_order.model_dump(exclude_none=True, exclude_unset=True))
|
|
77
|
+
return salesOrder.from_json(response, self.client)
|
|
78
|
+
|
|
79
|
+
def delete_sales_order(self, id: int) -> bool:
|
|
80
|
+
"""
|
|
81
|
+
Delete a sales order by its ID.
|
|
82
|
+
|
|
83
|
+
Sends a DELETE request to the sales order endpoint to remove the specified
|
|
84
|
+
sales order from the system.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
id (int): The ID of the sales order to delete.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
bool: True if the sales order was successfully deleted, False otherwise.
|
|
91
|
+
"""
|
|
92
|
+
return self.client._delete(f"/{self.endpoint}/{str(id)}")
|
|
93
|
+
|
|
94
|
+
def query_sales_orders(
|
|
95
|
+
self,
|
|
96
|
+
*,
|
|
97
|
+
query: Optional[str] = None,
|
|
98
|
+
sort: Optional[Dict[str, str]] = None,
|
|
99
|
+
filter: Optional[Dict[str, Any]] = None,
|
|
100
|
+
all: bool = False,
|
|
101
|
+
limit: int = 1000,
|
|
102
|
+
start: int = 0,
|
|
103
|
+
**extra_params
|
|
104
|
+
) -> List["salesOrder"]:
|
|
105
|
+
"""
|
|
106
|
+
Query sales orders with optional full-text search, filtering, multi-field sorting, and pagination.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
q (str, optional): Full-text search string.
|
|
110
|
+
sort (dict, optional): Dictionary of sorting rules (e.g., {"orderDate": "desc", "orderNo": "asc"}).
|
|
111
|
+
filter (dict, optional): Dictionary of filters to apply (will be JSON-encoded and URL-safe).
|
|
112
|
+
all (bool, optional): If True, retrieves all pages of results.
|
|
113
|
+
limit (int, optional): Number of results per page (max 1000).
|
|
114
|
+
start (int, optional): Starting offset for pagination.
|
|
115
|
+
**extra_params: Any additional parameters to include in the query.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
List[salesOrder]: List of wrapped sales order resources.
|
|
119
|
+
"""
|
|
120
|
+
return self.client._query(
|
|
121
|
+
endpoint=self.endpoint,
|
|
122
|
+
resource_cls=salesOrder,
|
|
123
|
+
query=query,
|
|
124
|
+
sort=sort,
|
|
125
|
+
filter=filter,
|
|
126
|
+
all=all,
|
|
127
|
+
limit=limit,
|
|
128
|
+
start=start,
|
|
129
|
+
**extra_params
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class InvoiceClient():
|
|
134
|
+
|
|
135
|
+
def __init__(self, client : SpireClient):
|
|
136
|
+
self.client = client
|
|
137
|
+
self.endpoint = "sales/invoices"
|
|
138
|
+
|
|
139
|
+
def get_invoice(self, id: int) -> 'invoice':
|
|
140
|
+
"""
|
|
141
|
+
Retrieve a sales invoice by its ID.
|
|
142
|
+
|
|
143
|
+
Sends a GET request to the invoices endpoint to fetch the invoice data.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
id (int): The ID of the invoice to retrieve.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
invoice: An invoice instance created from the response data.
|
|
150
|
+
"""
|
|
151
|
+
response = self.client._get(f"/{self.endpoint}/{id}")
|
|
152
|
+
return invoice.from_json(response, self.client)
|
|
153
|
+
|
|
154
|
+
def update_invoice(self, id: int, invoice : Invoice) -> 'invoice':
|
|
155
|
+
"""
|
|
156
|
+
Update an existing invoice by ID.
|
|
157
|
+
|
|
158
|
+
Sends a PUT request with updated invoice data to the invoices endpoint.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
id (int): The ID of the invoice to update.
|
|
162
|
+
invoice (Invoice): The Invoice model instance containing updated data.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
invoice: The updated invoice instance created from the response data.
|
|
166
|
+
"""
|
|
167
|
+
response = self.client._put(f"/{self.endpoint}/{id}", json=invoice.model_dump(exclude_none=True, exclude_unset=True))
|
|
168
|
+
return invoice.from_json(response, self.client)
|
|
169
|
+
|
|
170
|
+
def query_invoices(
|
|
171
|
+
self,
|
|
172
|
+
*,
|
|
173
|
+
query: Optional[str] = None,
|
|
174
|
+
sort: Optional[Dict[str, str]] = None,
|
|
175
|
+
filter: Optional[Dict[str, Any]] = None,
|
|
176
|
+
all: bool = False,
|
|
177
|
+
limit: int = 1000,
|
|
178
|
+
start: int = 0,
|
|
179
|
+
**extra_params
|
|
180
|
+
) -> List["salesOrder"]:
|
|
181
|
+
"""
|
|
182
|
+
Query invoices with optional full-text search, filtering, multi-field sorting, and pagination.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
q (str, optional): Full-text search string.
|
|
186
|
+
sort (dict, optional): Dictionary of sorting rules (e.g., {"orderDate": "desc", "orderNo": "asc"}).
|
|
187
|
+
filter (dict, optional): Dictionary of filters to apply (will be JSON-encoded and URL-safe).
|
|
188
|
+
all (bool, optional): If True, retrieves all pages of results.
|
|
189
|
+
limit (int, optional): Number of results per page (max 1000).
|
|
190
|
+
start (int, optional): Starting offset for pagination.
|
|
191
|
+
**extra_params: Any additional parameters to include in the query.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
List[invoice]: List of wrapped invoice resources.
|
|
195
|
+
"""
|
|
196
|
+
return self.client._query(
|
|
197
|
+
endpoint=self.endpoint,
|
|
198
|
+
resource_cls=invoice,
|
|
199
|
+
query=query,
|
|
200
|
+
sort=sort,
|
|
201
|
+
filter=filter,
|
|
202
|
+
all=all,
|
|
203
|
+
limit=limit,
|
|
204
|
+
start=start,
|
|
205
|
+
**extra_params
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class salesOrder(APIResource[SalesOrder]):
|
|
210
|
+
endpoint = "sales/orders/"
|
|
211
|
+
Model = SalesOrder
|
|
212
|
+
|
|
213
|
+
def invoice(self):
|
|
214
|
+
"""
|
|
215
|
+
Invoice the current sales order.
|
|
216
|
+
|
|
217
|
+
Sends a POST request to create an invoice for this sales order.
|
|
218
|
+
Note that quotes (salesOrder with type "Q") cannot be invoiced.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
invoice: The created invoice object if successful.
|
|
222
|
+
dict: The full response if invoicing failed.
|
|
223
|
+
|
|
224
|
+
"""
|
|
225
|
+
response = self._client._post(f"/{self.endpoint}/{str(self.id)}/invoice")
|
|
226
|
+
if response.get('status_code') == 201:
|
|
227
|
+
location = response.get('headers').get('location')
|
|
228
|
+
parsed_url = urlparse(location)
|
|
229
|
+
path_segments = parsed_url.path.rstrip("/").split("/")
|
|
230
|
+
id = path_segments[-1]
|
|
231
|
+
return InvoiceClient(client=self._client).get_invoice(id)
|
|
232
|
+
else:
|
|
233
|
+
error_message = response.get('content')
|
|
234
|
+
raise CreateRequestError(self.endpoint, status_code=response.get('status_code'), error_message=error_message)
|
|
235
|
+
|
|
236
|
+
def process(self) -> 'salesOrder':
|
|
237
|
+
"""
|
|
238
|
+
Set the status of the salesOrder to 'Processed' (status = 'P').
|
|
239
|
+
|
|
240
|
+
Sends a PUT request to update the status field of this Sales Oder.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
salesOrder: The updated salesOrder object reflecting the new status.
|
|
244
|
+
"""
|
|
245
|
+
response = self._client._put(
|
|
246
|
+
f"/{self.endpoint}/{str(self.id)}",
|
|
247
|
+
json={"status": "P"}
|
|
248
|
+
)
|
|
249
|
+
return salesOrder.from_json(response, self._client)
|
|
250
|
+
|
|
251
|
+
def delete(self):
|
|
252
|
+
"""
|
|
253
|
+
Cancels or deletes the sales order.
|
|
254
|
+
|
|
255
|
+
Sends a DELETE request to the API to remove the sales order with the current ID.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
bool: True if the order was successfully deleted (HTTP 204 or 200), False otherwise.
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
return self._client._delete(f"/{self.endpoint}/{str(self.id)}")
|
|
262
|
+
|
|
263
|
+
def update(self, order: "salesOrder" = None) -> 'salesOrder':
|
|
264
|
+
"""
|
|
265
|
+
Update the sales order.
|
|
266
|
+
|
|
267
|
+
If no order object is provided, updates the current instance on the server.
|
|
268
|
+
If an order object is provided, updates the sales order using the given data.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
order (salesOrder, optional): An optional salesOrder instance to use for the update.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
salesOrder: The updated salesOrder object reflecting the new status.
|
|
275
|
+
"""
|
|
276
|
+
data = order.model_dump(exclude_unset=True, exclude_none=True) if order else self.model_dump(exclude_unset=True, exclude_none=True)
|
|
277
|
+
response = self._client._put(f"/{self.endpoint}/{str(self.id)}", json=data)
|
|
278
|
+
return salesOrder.from_json(response, self._client)
|
|
279
|
+
|
|
280
|
+
class invoice(APIResource[Invoice]):
|
|
281
|
+
endpoint = "sales/invoices/"
|
|
282
|
+
Model = Invoice
|
|
283
|
+
|
|
284
|
+
def reverse(self):
|
|
285
|
+
"""
|
|
286
|
+
Convert this invoice into a sales order.
|
|
287
|
+
|
|
288
|
+
This method uses the internal model of the invoice to create a new sales order
|
|
289
|
+
using the `create_sales_order_from_invoice` utility function. It then submits the
|
|
290
|
+
order through the OrdersClient.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
salesOrder: The created sales order instance returned by the API.
|
|
294
|
+
"""
|
|
295
|
+
order_converted = utils.create_sales_order_from_invoice(self._model)
|
|
296
|
+
return OrdersClient(self._client).create_sales_order(order_converted)
|
|
297
|
+
|
|
298
|
+
def update(self , invoice_: "Invoice" = None) -> 'invoice':
|
|
299
|
+
"""
|
|
300
|
+
Update this invoice.
|
|
301
|
+
|
|
302
|
+
Sends a PUT request with updated invoice data to the invoices endpoint.
|
|
303
|
+
If no order object is provided, updates the current instance on the server.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
invoice (Invoice): The Invoice model instance containing updated data.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
invoice: The updated invoice instance created from the response data.
|
|
310
|
+
"""
|
|
311
|
+
data = invoice_.model_dump(exclude_unset=True, exclude_none=True) if invoice_ else self.model_dump(exclude_unset=True, exclude_none=True)
|
|
312
|
+
response = self._client._put(f"/{self.endpoint}/{str(self.id)}", json=data)
|
|
313
|
+
return invoice.from_json(response, self._client)
|
spyre/spire.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from client import SpireClient
|
|
2
|
+
from sales import *
|
|
3
|
+
from customers import *
|
|
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
|
+
|