spyreapi 0.0.1__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.1/LICENSE +21 -0
- spyreapi-0.0.1/MANIFEST.in +2 -0
- spyreapi-0.0.1/PKG-INFO +37 -0
- spyreapi-0.0.1/README.md +22 -0
- spyreapi-0.0.1/pyproject.toml +23 -0
- spyreapi-0.0.1/setup.cfg +4 -0
- spyreapi-0.0.1/src/spyre/Exceptions.py +45 -0
- spyreapi-0.0.1/src/spyre/Models/__init__.py +0 -0
- spyreapi-0.0.1/src/spyre/Models/customers_models.py +40 -0
- spyreapi-0.0.1/src/spyre/Models/inventory_models.py +160 -0
- spyreapi-0.0.1/src/spyre/Models/sales_models.py +170 -0
- spyreapi-0.0.1/src/spyre/Models/shared_models.py +96 -0
- spyreapi-0.0.1/src/spyre/__init__.py +22 -0
- spyreapi-0.0.1/src/spyre/client.py +208 -0
- spyreapi-0.0.1/src/spyre/config.py +6 -0
- spyreapi-0.0.1/src/spyre/customers.py +157 -0
- spyreapi-0.0.1/src/spyre/inventory.py +344 -0
- spyreapi-0.0.1/src/spyre/sales.py +313 -0
- spyreapi-0.0.1/src/spyre/spire.py +14 -0
- spyreapi-0.0.1/src/spyre/utils.py +130 -0
- spyreapi-0.0.1/src/spyreapi.egg-info/PKG-INFO +37 -0
- spyreapi-0.0.1/src/spyreapi.egg-info/SOURCES.txt +23 -0
- spyreapi-0.0.1/src/spyreapi.egg-info/dependency_links.txt +1 -0
- spyreapi-0.0.1/src/spyreapi.egg-info/top_level.txt +1 -0
- spyreapi-0.0.1/tests/testing.py +381 -0
spyreapi-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2025] [2025]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
spyreapi-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spyreapi
|
|
3
|
+
Version: 0.0.1
|
|
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
|
+
Author-email: Sanjid Sharaf <sanjidsharaf1@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sanjid-sharaf/spyre/
|
|
8
|
+
Project-URL: Issues, https://github.com/sanjid-sharaf/spyre/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# Spire API Python Client
|
|
17
|
+
|
|
18
|
+
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.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## โจ Features
|
|
23
|
+
|
|
24
|
+
- โ
Object-oriented resource wrappers for each module (e.g., `salesOrder`, `invoice`, `item`)
|
|
25
|
+
- ๐ Full-text search via `q` parameter
|
|
26
|
+
- ๐ Pagination with `start` and `limit` support
|
|
27
|
+
- ๐งพ JSON-based advanced filtering (supports `$gt`, `$lt`, `$in`, `$or`, etc.)
|
|
28
|
+
- โ๏ธ Multi-field sorting with ascending/descending control
|
|
29
|
+
- ๐ง Clean abstraction layer for API endpoints
|
|
30
|
+
- ๐ฆ Powered by `pydantic` models for validation
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## ๐ฆ Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install -r requirements.txt
|
spyreapi-0.0.1/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
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
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "spyreapi"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Sanjid Sharaf", email="sanjidsharaf1@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "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."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
license = "MIT"
|
|
19
|
+
license-files = ["LICEN[CS]E*"]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/sanjid-sharaf/spyre/"
|
|
23
|
+
Issues = "https://github.com/sanjid-sharaf/spyre/issues"
|
spyreapi-0.0.1/setup.cfg
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
>Cannot Convert Invoice to Quote
|
|
4
|
+
>Inactive Item
|
|
5
|
+
>invalide Syntax for date time
|
|
6
|
+
>A database error has occurred:\n\nA value passed to the database was too long for the column - Country Name
|
|
7
|
+
>Order Number Exists
|
|
8
|
+
>Invalid Customer
|
|
9
|
+
>Missing Parent/Child errror -> id of addresses
|
|
10
|
+
|
|
11
|
+
Contacts 3
|
|
12
|
+
Request Failure
|
|
13
|
+
Not Found
|
|
14
|
+
invalid Field for filter
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
{'status_code': 422, 'url': 'https://red-wave-8362.spirelan.com:10880/api/v2/companies/intertest/sales/orders/31308/invoice',
|
|
18
|
+
'content': {'type': 'error', 'message': 'Cannot post a deleted order', 'traceback': '', 'error_type': 'BusinessViolationError'}}
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
class CreateRequestError(Exception):
|
|
22
|
+
"""
|
|
23
|
+
Exception raised when a create (POST) request to the API fails.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, endpoint: str, status_code: int, error_message: str = "", response_body: dict = None):
|
|
27
|
+
self.endpoint = endpoint
|
|
28
|
+
self.status_code = status_code
|
|
29
|
+
self.error_message = error_message or "Unknown error occurred"
|
|
30
|
+
self.response_body = response_body or {}
|
|
31
|
+
|
|
32
|
+
super().__init__(self.__str__())
|
|
33
|
+
|
|
34
|
+
def __str__(self):
|
|
35
|
+
return (
|
|
36
|
+
f"Create request to '{self.endpoint}' failed with status {self.status_code}: {self.error_message}"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def to_dict(self):
|
|
40
|
+
return {
|
|
41
|
+
"endpoint": self.endpoint,
|
|
42
|
+
"status_code": self.status_code,
|
|
43
|
+
"error_message": self.error_message,
|
|
44
|
+
"response_body": self.response_body,
|
|
45
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import List, Optional, Dict, Union, Any
|
|
2
|
+
from pydantic import BaseModel, model_validator
|
|
3
|
+
from Models.shared_models import *
|
|
4
|
+
|
|
5
|
+
class Customer(BaseModel):
|
|
6
|
+
id: Optional[int] = None
|
|
7
|
+
code: Optional[str] = None
|
|
8
|
+
customerNo: Optional[str] = None
|
|
9
|
+
name: Optional[str] = None
|
|
10
|
+
foregroundColor: Optional[int] = None
|
|
11
|
+
backgroundColor: Optional[int] = None
|
|
12
|
+
hold: Optional[bool] = None
|
|
13
|
+
status: Optional[str] = None
|
|
14
|
+
reference: Optional[str] = None
|
|
15
|
+
address: Optional[Address] = None
|
|
16
|
+
shippingAddresses: Optional[List[Address]] = None
|
|
17
|
+
paymentTerms: Optional[Dict[str, Any]] = None
|
|
18
|
+
applyFinanceCharges: Optional[bool] = None
|
|
19
|
+
statementType: Optional[str] = None
|
|
20
|
+
creditType: Optional[int] = None
|
|
21
|
+
creditLimit: Optional[str] = None
|
|
22
|
+
creditBalance: Optional[str] = None
|
|
23
|
+
creditApprovedBy: Optional[str] = None
|
|
24
|
+
creditApprovedDate: Optional[str] = None
|
|
25
|
+
currency: Optional[str] = None
|
|
26
|
+
userDef1: Optional[str] = None
|
|
27
|
+
userDef2: Optional[str] = None
|
|
28
|
+
discount: Optional[str] = None
|
|
29
|
+
receivableAccount: Optional[str] = None
|
|
30
|
+
defaultShipTo: Optional[str] = None
|
|
31
|
+
specialCode: Optional[str] = None
|
|
32
|
+
upload: Optional[bool] = None
|
|
33
|
+
lastModified: Optional[str] = None
|
|
34
|
+
paymentProviderId: Optional[int] = None
|
|
35
|
+
udf: Optional[Dict[str, Any]] = None
|
|
36
|
+
createdBy: Optional[str] = None
|
|
37
|
+
modifiedBy: Optional[str] = None
|
|
38
|
+
created: Optional[str] = None
|
|
39
|
+
modified: Optional[str] = None
|
|
40
|
+
links: Optional[Dict[str, str]] = None
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from typing import List, Dict, Optional, Union
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UPC(BaseModel):
|
|
7
|
+
id: Optional[int] = None
|
|
8
|
+
whse: Optional[str] = None
|
|
9
|
+
partNo: Optional[str] = None
|
|
10
|
+
inventory: Optional[Dict] = None
|
|
11
|
+
uomCode: Optional[str] = None
|
|
12
|
+
upc: Optional[str] = None
|
|
13
|
+
created: Optional[str] = None
|
|
14
|
+
createdBy: Optional[str] = None
|
|
15
|
+
modified: Optional[str] = None
|
|
16
|
+
modifiedBy: Optional[str] = None
|
|
17
|
+
links: Optional[Dict[str, str]] = None
|
|
18
|
+
|
|
19
|
+
class Vendor(BaseModel):
|
|
20
|
+
id: Optional[int] = None
|
|
21
|
+
vendorNo: Optional[str] = None
|
|
22
|
+
name: Optional[str] = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class UnitOfMeasure(BaseModel):
|
|
26
|
+
id: Optional[int] = None
|
|
27
|
+
code: Optional[str] = None
|
|
28
|
+
description: Optional[str] = None
|
|
29
|
+
location: Optional[str] = None
|
|
30
|
+
weight: Optional[str] = None
|
|
31
|
+
buyUOM: Optional[bool] = None
|
|
32
|
+
sellUOM: Optional[bool] = None
|
|
33
|
+
allowFractionalQty: Optional[bool] = None
|
|
34
|
+
quantityFactor: Optional[str] = None
|
|
35
|
+
directFactor: Optional[bool] = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Pricing(BaseModel):
|
|
39
|
+
id: Optional[int] = None
|
|
40
|
+
sellPrices: Optional[List[str]] = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ItemUDF(BaseModel):
|
|
44
|
+
desc: Optional[str] = None
|
|
45
|
+
brand: Optional[str] = None
|
|
46
|
+
model: Optional[str] = None
|
|
47
|
+
title: Optional[str] = None
|
|
48
|
+
online: Optional[bool] = None
|
|
49
|
+
pk_qty: Optional[str] = None
|
|
50
|
+
volume: Optional[str] = None
|
|
51
|
+
presale: Optional[bool] = None
|
|
52
|
+
sds_url: Optional[str] = None
|
|
53
|
+
tds_url: Optional[str] = None
|
|
54
|
+
features: Optional[str] = None
|
|
55
|
+
includes: Optional[str] = None
|
|
56
|
+
keywords: Optional[str] = None
|
|
57
|
+
op1_name: Optional[str] = None
|
|
58
|
+
op2_name: Optional[str] = None
|
|
59
|
+
op3_name: Optional[str] = None
|
|
60
|
+
op4_name: Optional[str] = None
|
|
61
|
+
asm_depth: Optional[str] = None
|
|
62
|
+
asm_width: Optional[str] = None
|
|
63
|
+
category1: Optional[str] = None
|
|
64
|
+
category2: Optional[str] = None
|
|
65
|
+
category3: Optional[str] = None
|
|
66
|
+
category4: Optional[str] = None
|
|
67
|
+
op1_value: Optional[str] = None
|
|
68
|
+
op2_value: Optional[str] = None
|
|
69
|
+
op3_value: Optional[str] = None
|
|
70
|
+
op4_value: Optional[str] = None
|
|
71
|
+
pkg_depth: Optional[str] = None
|
|
72
|
+
pkg_width: Optional[str] = None
|
|
73
|
+
asm_height: Optional[str] = None
|
|
74
|
+
asm_length: Optional[str] = None
|
|
75
|
+
asm_weight: Optional[str] = None
|
|
76
|
+
html_specs: Optional[str] = None
|
|
77
|
+
image_urls: Optional[str] = None
|
|
78
|
+
is_variant: Optional[bool] = None
|
|
79
|
+
pkg_height: Optional[str] = None
|
|
80
|
+
pkg_length: Optional[str] = None
|
|
81
|
+
pkg_weight: Optional[str] = None
|
|
82
|
+
put_online: Optional[bool] = None
|
|
83
|
+
upc_needed: Optional[bool] = None
|
|
84
|
+
video_urls: Optional[str] = None
|
|
85
|
+
hdr_part_no: Optional[str] = None
|
|
86
|
+
applications: Optional[str] = None
|
|
87
|
+
sell_through: Optional[bool] = None
|
|
88
|
+
unique_title: Optional[str] = None
|
|
89
|
+
alternate_res: Optional[str] = None
|
|
90
|
+
asm_weight_op: Optional[str] = None
|
|
91
|
+
html_includes: Optional[str] = None
|
|
92
|
+
more_info_url: Optional[str] = None
|
|
93
|
+
specsheet_url: Optional[str] = None
|
|
94
|
+
html_desc_feat: Optional[str] = None
|
|
95
|
+
specifications: Optional[str] = None
|
|
96
|
+
html_dimensions: Optional[str] = None
|
|
97
|
+
special_delivery: Optional[bool] = None
|
|
98
|
+
alternate_res_src: Optional[str] = None
|
|
99
|
+
html_applications: Optional[str] = None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class InventoryItem(BaseModel):
|
|
103
|
+
id: Optional[int] = None
|
|
104
|
+
whse: Optional[str] = None
|
|
105
|
+
partNo: Optional[str] = None
|
|
106
|
+
description: Optional[str] = None
|
|
107
|
+
type: Optional[str] = None
|
|
108
|
+
status: Optional[int] = None
|
|
109
|
+
lotNumbered: Optional[bool] = None
|
|
110
|
+
serialized: Optional[bool] = None
|
|
111
|
+
availableQty: Optional[str] = None
|
|
112
|
+
onHandQty: Optional[str] = None
|
|
113
|
+
committedQty: Optional[str] = None
|
|
114
|
+
backorderQty: Optional[str] = None
|
|
115
|
+
onPurchaseQty: Optional[str] = None
|
|
116
|
+
foregroundColor: Optional[int] = None
|
|
117
|
+
backgroundColor: Optional[int] = None
|
|
118
|
+
primaryVendor: Optional[Vendor] = None
|
|
119
|
+
currentPONo: Optional[str] = None
|
|
120
|
+
poDueDate: Optional[str] = None
|
|
121
|
+
reorderPoint: Optional[str] = None
|
|
122
|
+
minimumBuyQty: Optional[str] = None
|
|
123
|
+
currentCost: Optional[str] = None
|
|
124
|
+
averageCost: Optional[str] = None
|
|
125
|
+
standardCost: Optional[str] = None
|
|
126
|
+
unitOfMeasures: Optional[Dict[str, UnitOfMeasure]] = None
|
|
127
|
+
buyMeasureCode: Optional[str] = None
|
|
128
|
+
stockMeasureCode: Optional[str] = None
|
|
129
|
+
sellMeasureCode: Optional[str] = None
|
|
130
|
+
alternatePartNo: Optional[str] = None
|
|
131
|
+
productCode: Optional[str] = None
|
|
132
|
+
groupNo: Optional[str] = None
|
|
133
|
+
salesDept: Optional[str] = None
|
|
134
|
+
userDef1: Optional[str] = None
|
|
135
|
+
userDef2: Optional[str] = None
|
|
136
|
+
discountable: Optional[bool] = None
|
|
137
|
+
weight: Optional[str] = None
|
|
138
|
+
packSize: Optional[str] = None
|
|
139
|
+
allowBackorders: Optional[bool] = None
|
|
140
|
+
allowReturns: Optional[bool] = None
|
|
141
|
+
dutyPct: Optional[str] = None
|
|
142
|
+
freightPct: Optional[str] = None
|
|
143
|
+
manufactureCountry: Optional[str] = None
|
|
144
|
+
harmonizedCode: Optional[str] = None
|
|
145
|
+
extendedDescription: Optional[str] = None
|
|
146
|
+
pricing: Optional[Dict[str, Pricing]] = None
|
|
147
|
+
salesTaxFlags: Optional[Dict[str, Union[str, int, float, bool]]] = None
|
|
148
|
+
images: Optional[List[str]] = None
|
|
149
|
+
defaultExpiryDate: Optional[str] = None
|
|
150
|
+
lotConsumeType: Optional[str] = None
|
|
151
|
+
upload: Optional[bool] = None
|
|
152
|
+
showOptions: Optional[bool] = None
|
|
153
|
+
lastModified: Optional[str] = None
|
|
154
|
+
levy: Optional[str] = None
|
|
155
|
+
udf: Optional[ItemUDF] = None
|
|
156
|
+
createdBy: Optional[str] = None
|
|
157
|
+
modifiedBy: Optional[str] = None
|
|
158
|
+
created: Optional[str] = None
|
|
159
|
+
modified: Optional[str] = None
|
|
160
|
+
links: Optional[Dict[str, Union[str, int, float, bool]]] = None
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from typing import List, Optional, Dict, Union
|
|
2
|
+
from pydantic import BaseModel, model_validator
|
|
3
|
+
from Models.shared_models import *
|
|
4
|
+
from Models.customers_models import Customer
|
|
5
|
+
|
|
6
|
+
class Inventory(BaseModel):
|
|
7
|
+
id: Optional[int] = None
|
|
8
|
+
whse: Optional[str] = None
|
|
9
|
+
partNo: Optional[str] = None
|
|
10
|
+
description: Optional[str] = None
|
|
11
|
+
|
|
12
|
+
class SalesOrderItem(BaseModel):
|
|
13
|
+
id: Optional[int] = None
|
|
14
|
+
orderNo: Optional[str] = None
|
|
15
|
+
sequence: Optional[int] = None
|
|
16
|
+
parentSequence: Optional[int] = None
|
|
17
|
+
inventory: Optional[Inventory] = None
|
|
18
|
+
serials: Optional[str] = None
|
|
19
|
+
whse: Optional[str] = None
|
|
20
|
+
partNo: Optional[str] = None
|
|
21
|
+
description: Optional[str] = None
|
|
22
|
+
comment: Optional[str] = None
|
|
23
|
+
orderQty: Optional[str] = None
|
|
24
|
+
committedQty: Optional[str] = None
|
|
25
|
+
backorderQty: Optional[str] = None
|
|
26
|
+
sellMeasure: Optional[str] = None
|
|
27
|
+
retailPrice: Optional[str] = None
|
|
28
|
+
unitPrice: Optional[str] = None
|
|
29
|
+
userPrice: Optional[bool] = None
|
|
30
|
+
discountable: Optional[bool] = None
|
|
31
|
+
discountPct: Optional[str] = None
|
|
32
|
+
discountAmt: Optional[str] = None
|
|
33
|
+
currentCost: Optional[str] = None
|
|
34
|
+
averageCost: Optional[str] = None
|
|
35
|
+
standardCost: Optional[str] = None
|
|
36
|
+
taxFlags: Optional[List[bool]] = None
|
|
37
|
+
vendor: Optional[str] = None
|
|
38
|
+
inventoryAccountNo: Optional[str] = None
|
|
39
|
+
revenueAccountNo: Optional[str] = None
|
|
40
|
+
costOfGoodsAccountNo: Optional[str] = None
|
|
41
|
+
levyCode: Optional[str] = None
|
|
42
|
+
referenceNo: Optional[str] = None
|
|
43
|
+
requiredDate: Optional[str] = None
|
|
44
|
+
extendedPriceOrdered: Optional[str] = None
|
|
45
|
+
extendedPriceCommitted: Optional[str] = None
|
|
46
|
+
kit: Optional[bool] = None
|
|
47
|
+
suppress: Optional[bool] = None
|
|
48
|
+
udf: Optional[dict] = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SalesOrder(BaseModel):
|
|
52
|
+
id: Optional[int] = None
|
|
53
|
+
orderNo: Optional[str] = None
|
|
54
|
+
division: Optional[str] = None
|
|
55
|
+
location: Optional[str] = None
|
|
56
|
+
profitCenter: Optional[str] = None
|
|
57
|
+
invoiceNo: Optional[str] = None
|
|
58
|
+
customer: Optional[Customer] = None
|
|
59
|
+
creditApprovedAmount: Optional[str] = None
|
|
60
|
+
creditApprovedDate: Optional[str] = None
|
|
61
|
+
creditApprovedUser: Optional[str] = None
|
|
62
|
+
currency: Optional[Currency] = None
|
|
63
|
+
status: Optional[str] = None
|
|
64
|
+
type: Optional[str] = None
|
|
65
|
+
hold: Optional[bool] = None
|
|
66
|
+
orderDate: Optional[str] = None
|
|
67
|
+
invoiceDate: Optional[str] = None
|
|
68
|
+
requiredDate: Optional[str] = None
|
|
69
|
+
quoteExpires: Optional[str] = None
|
|
70
|
+
recurrenceRule: Optional[str] = None
|
|
71
|
+
address: Optional[Address] = None
|
|
72
|
+
shippingAddress: Optional[Address] = None
|
|
73
|
+
contact: Optional[Contact] = None
|
|
74
|
+
customerPO: Optional[str] = None
|
|
75
|
+
batchNo: Optional[str] = None
|
|
76
|
+
fob: Optional[str] = None
|
|
77
|
+
incoterms: Optional[str] = None
|
|
78
|
+
incotermsPlace: Optional[str] = None
|
|
79
|
+
referenceNo: Optional[str] = None
|
|
80
|
+
shippingCarrier: Optional[str] = None
|
|
81
|
+
shipDate: Optional[str] = None
|
|
82
|
+
trackingNo: Optional[str] = None
|
|
83
|
+
termsCode: Optional[str] = None
|
|
84
|
+
termsText: Optional[str] = None
|
|
85
|
+
freight: Optional[str] = None
|
|
86
|
+
taxes: Optional[List[Tax]] = None
|
|
87
|
+
subtotal: Optional[str] = None
|
|
88
|
+
subtotalOrdered: Optional[str] = None
|
|
89
|
+
discount: Optional[str] = None
|
|
90
|
+
totalDiscount: Optional[str] = None
|
|
91
|
+
total: Optional[str] = None
|
|
92
|
+
totalOrdered: Optional[str] = None
|
|
93
|
+
totalCostCurrent: Optional[str] = None
|
|
94
|
+
totalCostAverage: Optional[str] = None
|
|
95
|
+
grossProfit: Optional[str] = None
|
|
96
|
+
items: Optional[List[SalesOrderItem]] = None
|
|
97
|
+
payments: Optional[List[dict]] = None
|
|
98
|
+
udf: Optional[dict] = None
|
|
99
|
+
createdBy: Optional[str] = None
|
|
100
|
+
modifiedBy: Optional[str] = None
|
|
101
|
+
created: Optional[str] = None
|
|
102
|
+
modified: Optional[str] = None
|
|
103
|
+
deletedBy: Optional[str] = None
|
|
104
|
+
deleted: Optional[str] = None
|
|
105
|
+
links: Optional[Dict[str, str]] = None
|
|
106
|
+
|
|
107
|
+
@model_validator(mode="before")
|
|
108
|
+
@classmethod
|
|
109
|
+
def clean_problematic_fields(cls, data: dict) -> dict:
|
|
110
|
+
if data.get("currency") == "":
|
|
111
|
+
data["currency"] = None
|
|
112
|
+
|
|
113
|
+
contact = data.get("contact")
|
|
114
|
+
if contact:
|
|
115
|
+
for field in ("phone", "fax"):
|
|
116
|
+
phone_data = contact.get(field)
|
|
117
|
+
if isinstance(phone_data, dict) and phone_data.get("number") is None:
|
|
118
|
+
phone_data["number"] = ""
|
|
119
|
+
return data
|
|
120
|
+
|
|
121
|
+
class Invoice(BaseModel):
|
|
122
|
+
id: Optional[int] = None
|
|
123
|
+
invoiceNo: Optional[str] = None
|
|
124
|
+
orderNo: Optional[str] = None
|
|
125
|
+
division: Optional[str] = None
|
|
126
|
+
location: Optional[str] = None
|
|
127
|
+
profitCenter: Optional[str] = None
|
|
128
|
+
customer: Optional['Customer'] = None
|
|
129
|
+
currency: Optional['Currency'] = None
|
|
130
|
+
orderDate: Optional[str] = None
|
|
131
|
+
invoiceDate: Optional[str] = None
|
|
132
|
+
requiredDate: Optional[str] = None
|
|
133
|
+
address: Optional['Address'] = None
|
|
134
|
+
shippingAddress: Optional['Address'] = None
|
|
135
|
+
customerPO: Optional[str] = None
|
|
136
|
+
fob: Optional[str] = None
|
|
137
|
+
incoterms: Optional[str] = None
|
|
138
|
+
incotermsPlace: Optional[str] = None
|
|
139
|
+
referenceNo: Optional[str] = None
|
|
140
|
+
shippingCarrier: Optional[str] = None
|
|
141
|
+
shipDate: Optional[str] = None
|
|
142
|
+
trackingNo: Optional[str] = None
|
|
143
|
+
termsCode: Optional[str] = None
|
|
144
|
+
termsText: Optional[str] = None
|
|
145
|
+
freight: Optional[str] = None
|
|
146
|
+
taxes: Optional[List['Tax']] = None
|
|
147
|
+
subtotal: Optional[str] = None
|
|
148
|
+
total: Optional[str] = None
|
|
149
|
+
items: Optional[List['SalesOrderItem']] = None
|
|
150
|
+
payments: Optional[List[dict]] = None
|
|
151
|
+
udf: Optional[dict] = None
|
|
152
|
+
createdBy: Optional[str] = None
|
|
153
|
+
modifiedBy: Optional[str] = None
|
|
154
|
+
created: Optional[str] = None
|
|
155
|
+
modified: Optional[str] = None
|
|
156
|
+
links: Optional[Dict[str, str]] = None
|
|
157
|
+
|
|
158
|
+
@model_validator(mode="before")
|
|
159
|
+
@classmethod
|
|
160
|
+
def clean_problematic_fields(cls, data: dict) -> dict:
|
|
161
|
+
if data.get("currency") == "":
|
|
162
|
+
data["currency"] = None
|
|
163
|
+
|
|
164
|
+
contact = data.get("contact")
|
|
165
|
+
if contact:
|
|
166
|
+
for field in ("phone", "fax"):
|
|
167
|
+
phone_data = contact.get(field)
|
|
168
|
+
if isinstance(phone_data, dict) and phone_data.get("number") is None:
|
|
169
|
+
phone_data["number"] = ""
|
|
170
|
+
return data
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import List, Optional, Dict, Union
|
|
2
|
+
from pydantic import BaseModel, model_validator
|
|
3
|
+
|
|
4
|
+
class PhoneFax(BaseModel):
|
|
5
|
+
number: str
|
|
6
|
+
format: Optional[int] = 1
|
|
7
|
+
|
|
8
|
+
class Contact(BaseModel):
|
|
9
|
+
id: Optional[int] = None
|
|
10
|
+
contact_type: Optional[dict] = None
|
|
11
|
+
name: Optional[str] = None
|
|
12
|
+
email: Optional[str] = None
|
|
13
|
+
phone: Optional[PhoneFax] = None
|
|
14
|
+
fax: Optional[PhoneFax] = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Salesperson(BaseModel):
|
|
18
|
+
code: Optional[str] = None
|
|
19
|
+
name: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
class Territory(BaseModel):
|
|
22
|
+
code: Optional[str] = None
|
|
23
|
+
description: Optional[str] = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Currency(BaseModel):
|
|
27
|
+
id: Optional[int] = None
|
|
28
|
+
code: Optional[str] = None
|
|
29
|
+
description: Optional[str] = None
|
|
30
|
+
country: Optional[str] = None
|
|
31
|
+
units: Optional[str] = None
|
|
32
|
+
fraction: Optional[str] = None
|
|
33
|
+
symbol: Optional[str] = None
|
|
34
|
+
decimalPlaces: Optional[int] = None
|
|
35
|
+
symbolPosition: Optional[str] = None
|
|
36
|
+
rate: Optional[str] = None
|
|
37
|
+
rateMethod: Optional[str] = None
|
|
38
|
+
glAccountNo: Optional[str] = None
|
|
39
|
+
thousandsSeparator: Optional[str] = None
|
|
40
|
+
lastYearRate: Optional[List[str]] = None
|
|
41
|
+
thisYearRate: Optional[List[str]] = None
|
|
42
|
+
nextYearRate: Optional[List[str]] = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Tax(BaseModel):
|
|
46
|
+
code: Optional[int] = None
|
|
47
|
+
name: Optional[str] = None
|
|
48
|
+
shortName: Optional[str] = None
|
|
49
|
+
rate: Optional[Union[str, int]] = None
|
|
50
|
+
exemptNo: Optional[str] = None
|
|
51
|
+
total: Optional[Union[str, int]] = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Address(BaseModel):
|
|
55
|
+
id: Optional[int] = None
|
|
56
|
+
type: Optional[str] = None
|
|
57
|
+
linkTable: Optional[str] = None
|
|
58
|
+
linkType: Optional[str] = None
|
|
59
|
+
linkNo: Optional[str] = None
|
|
60
|
+
shipId: Optional[str] = None
|
|
61
|
+
name: Optional[str] = None
|
|
62
|
+
line1: Optional[str] = None
|
|
63
|
+
line2: Optional[str] = None
|
|
64
|
+
line3: Optional[str] = None
|
|
65
|
+
line4: Optional[str] = None
|
|
66
|
+
city: Optional[str] = None
|
|
67
|
+
postalCode: Optional[str] = None
|
|
68
|
+
provState: Optional[str] = None
|
|
69
|
+
country: Optional[str] = None
|
|
70
|
+
phone: Optional[PhoneFax] = None
|
|
71
|
+
fax: Optional[PhoneFax] = None
|
|
72
|
+
email: Optional[str] = None
|
|
73
|
+
website: Optional[str] = None
|
|
74
|
+
shipCode: Optional[str] = None
|
|
75
|
+
shipDescription: Optional[str] = None
|
|
76
|
+
salesperson: Optional[Salesperson] = None
|
|
77
|
+
territory: Optional[Territory] = None
|
|
78
|
+
sellLevel: Optional[int] = None
|
|
79
|
+
glAccount: Optional[str] = None
|
|
80
|
+
defaultWarehouse: Optional[str] = None
|
|
81
|
+
udf: Optional[dict] = None
|
|
82
|
+
created: Optional[str] = None
|
|
83
|
+
modified: Optional[str] = None
|
|
84
|
+
contacts: Optional[List[Contact]] = None
|
|
85
|
+
salesTaxes: Optional[List[Dict[str, Union[int, str]]]] = None
|
|
86
|
+
|
|
87
|
+
@model_validator(mode="before")
|
|
88
|
+
@classmethod
|
|
89
|
+
def limit_contacts_to_three(cls, data):
|
|
90
|
+
"""Limits contacts to 3 per address. The Spire API only allows Creatig/Updating Addresses with contacts."""
|
|
91
|
+
|
|
92
|
+
if isinstance(data, dict):
|
|
93
|
+
contacts = data.get('contacts')
|
|
94
|
+
if isinstance(contacts, list) and len(contacts) > 3:
|
|
95
|
+
data['contacts'] = contacts[:3]
|
|
96
|
+
return data
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from .client import SpireClient
|
|
2
|
+
from .inventory import InventoryClient
|
|
3
|
+
from .sales import OrdersClient, InvoiceClient, salesOrder, invoice
|
|
4
|
+
from .Models.sales_models import SalesOrder, SalesOrderItem
|
|
5
|
+
from .Models.inventory_models import InventoryItem, Vendor, UnitOfMeasure, Pricing, UPC
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"SpireClient",
|
|
9
|
+
"InventoryClient",
|
|
10
|
+
"SalesOrderClient",
|
|
11
|
+
"SalesOrder",
|
|
12
|
+
"SalesOrderItem",
|
|
13
|
+
"InventoryItem",
|
|
14
|
+
"Vendor",
|
|
15
|
+
"UnitOfMeasure",
|
|
16
|
+
"Pricing",
|
|
17
|
+
"UPC",
|
|
18
|
+
"OrdersClient",
|
|
19
|
+
"InvoiceClient",
|
|
20
|
+
"salesOrder",
|
|
21
|
+
"invoice"
|
|
22
|
+
]
|