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/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
+