spyreapi 0.0.2__tar.gz → 0.0.3__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.3/MANIFEST.in +4 -0
- {spyreapi-0.0.2/src/spyreapi.egg-info → spyreapi-0.0.3}/PKG-INFO +18 -1
- {spyreapi-0.0.2 → spyreapi-0.0.3}/README.md +17 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/pyproject.toml +1 -1
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/Models/sales_models.py +1 -1
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/Models/shared_models.py +30 -1
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/client.py +6 -1
- spyreapi-0.0.3/src/spyre/crm.py +26 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/customers.py +1 -1
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/sales.py +55 -2
- {spyreapi-0.0.2 → spyreapi-0.0.3/src/spyreapi.egg-info}/PKG-INFO +18 -1
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyreapi.egg-info/SOURCES.txt +2 -2
- spyreapi-0.0.2/MANIFEST.in +0 -3
- spyreapi-0.0.2/tests/testing.py +0 -378
- {spyreapi-0.0.2 → spyreapi-0.0.3}/LICENSE +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/setup.cfg +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/Exceptions.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/Models/__init__.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/Models/customers_models.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/Models/inventory_models.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/__init__.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/config.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/inventory.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/spire.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyre/utils.py +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyreapi.egg-info/dependency_links.txt +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/src/spyreapi.egg-info/requires.txt +0 -0
- {spyreapi-0.0.2 → spyreapi-0.0.3}/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.3
|
|
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,20 @@ 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
|
+
- Before using the client, set up your environment configuration:
|
|
52
|
+
|
|
53
|
+
### Add your Base URL to your spire server to a `.env` file
|
|
54
|
+
|
|
55
|
+
- In your project root, create a `.env` file to securely store your Spire configuration.
|
|
56
|
+
- Add the following variable:
|
|
57
|
+
|
|
58
|
+
```env
|
|
59
|
+
BASE_URL = https://{your-spire-domain}/api/v2/companies/
|
|
60
|
+
```
|
|
61
|
+
- Replace {your-spire-domain} with your actual Spire server's hostname or IP address.
|
|
@@ -20,3 +20,20 @@ A robust and extensible Python client for interacting with the [Spire Business S
|
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
pip install -r requirements.txt
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## ⚙️ Configuration
|
|
28
|
+
|
|
29
|
+
- Before using the client, set up your environment configuration:
|
|
30
|
+
|
|
31
|
+
### Add your Base URL to your spire server to a `.env` file
|
|
32
|
+
|
|
33
|
+
- In your project root, create a `.env` file to securely store your Spire configuration.
|
|
34
|
+
- Add the following variable:
|
|
35
|
+
|
|
36
|
+
```env
|
|
37
|
+
BASE_URL = https://{your-spire-domain}/api/v2/companies/
|
|
38
|
+
```
|
|
39
|
+
- Replace {your-spire-domain} with your actual Spire server's hostname or IP address.
|
|
@@ -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
|
|
@@ -116,8 +116,13 @@ class SpireClient():
|
|
|
116
116
|
|
|
117
117
|
if query:
|
|
118
118
|
params.append(("q", query))
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
if filter:
|
|
121
|
+
model_fields = resource_cls.Model.model_fields.keys()
|
|
122
|
+
invalid_fields = [key for key in filter.keys() if key not in model_fields]
|
|
123
|
+
if invalid_fields:
|
|
124
|
+
raise ValueError(f"Invalid filter field(s): {invalid_fields}. for {resource_cls.Model.__name__} ")
|
|
125
|
+
|
|
121
126
|
encoded_filter = json.dumps(filter)
|
|
122
127
|
params.append(("filter", encoded_filter))
|
|
123
128
|
|
|
@@ -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
|
|
|
@@ -277,6 +304,30 @@ class salesOrder(APIResource[SalesOrder]):
|
|
|
277
304
|
response = self._client._put(f"/{self.endpoint}/{str(self.id)}", json=data)
|
|
278
305
|
return salesOrder.from_json(response, self._client)
|
|
279
306
|
|
|
307
|
+
def add_note(self, note_body : "str" , note_subject : "str" = "Note") -> note:
|
|
308
|
+
"""
|
|
309
|
+
Add a note to this sales order
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
note_body (str): the body of the note.
|
|
313
|
+
note_subject (str, "Note"): the subject of the note. the defualt value is just "Note"
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
note: The note created.
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
note_model = Note(body=note_body, subject=note_subject)
|
|
320
|
+
response = self._client._post(f"/{self.endpoint}/{str(self.id)}/notes/", json=note_model.model_dump(exclude_unset=True, exclude_none=True))
|
|
321
|
+
if response.get('status_code') == 201:
|
|
322
|
+
location = response.get('headers').get('location')
|
|
323
|
+
parsed_url = urlparse(location)
|
|
324
|
+
path_segments = parsed_url.path.rstrip("/").split("/")
|
|
325
|
+
id = path_segments[-1]
|
|
326
|
+
return CRMClient(client=self._client).get_note(id)
|
|
327
|
+
else:
|
|
328
|
+
error_message = response.get('content')
|
|
329
|
+
raise CreateRequestError(f"/{self.endpoint}/{str(self.id)}/notes/", status_code=response.get('status_code'), error_message=error_message)
|
|
330
|
+
|
|
280
331
|
class invoice(APIResource[Invoice]):
|
|
281
332
|
endpoint = "sales/invoices/"
|
|
282
333
|
Model = Invoice
|
|
@@ -310,4 +361,6 @@ class invoice(APIResource[Invoice]):
|
|
|
310
361
|
"""
|
|
311
362
|
data = invoice_.model_dump(exclude_unset=True, exclude_none=True) if invoice_ else self.model_dump(exclude_unset=True, exclude_none=True)
|
|
312
363
|
response = self._client._put(f"/{self.endpoint}/{str(self.id)}", json=data)
|
|
313
|
-
return invoice.from_json(response, self._client)
|
|
364
|
+
return invoice.from_json(response, self._client)
|
|
365
|
+
|
|
366
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spyreapi
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
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,20 @@ 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
|
+
- Before using the client, set up your environment configuration:
|
|
52
|
+
|
|
53
|
+
### Add your Base URL to your spire server to a `.env` file
|
|
54
|
+
|
|
55
|
+
- In your project root, create a `.env` file to securely store your Spire configuration.
|
|
56
|
+
- Add the following variable:
|
|
57
|
+
|
|
58
|
+
```env
|
|
59
|
+
BASE_URL = https://{your-spire-domain}/api/v2/companies/
|
|
60
|
+
```
|
|
61
|
+
- Replace {your-spire-domain} with your actual Spire server's hostname or IP address.
|
|
@@ -6,6 +6,7 @@ src/spyre/Exceptions.py
|
|
|
6
6
|
src/spyre/__init__.py
|
|
7
7
|
src/spyre/client.py
|
|
8
8
|
src/spyre/config.py
|
|
9
|
+
src/spyre/crm.py
|
|
9
10
|
src/spyre/customers.py
|
|
10
11
|
src/spyre/inventory.py
|
|
11
12
|
src/spyre/sales.py
|
|
@@ -20,5 +21,4 @@ src/spyreapi.egg-info/PKG-INFO
|
|
|
20
21
|
src/spyreapi.egg-info/SOURCES.txt
|
|
21
22
|
src/spyreapi.egg-info/dependency_links.txt
|
|
22
23
|
src/spyreapi.egg-info/requires.txt
|
|
23
|
-
src/spyreapi.egg-info/top_level.txt
|
|
24
|
-
tests/testing.py
|
|
24
|
+
src/spyreapi.egg-info/top_level.txt
|
spyreapi-0.0.2/MANIFEST.in
DELETED
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|