diff --git a/lightspeed_api/api/__init__.py b/lightspeed_api/api/__init__.py index 11bcaf9..92125b9 100644 --- a/lightspeed_api/api/__init__.py +++ b/lightspeed_api/api/__init__.py @@ -40,6 +40,8 @@ def _get_wrapper(self, url, raw=False, preload_relations=[], object_class=None, if raw: return data[data_fieldname] + if not data: + return None return self._unwrap_object_to_cls(data_object_class, data[data_fieldname]) def _create_forever_list(self, data, objcls, retrieval_func, search, preload_relations): @@ -79,6 +81,8 @@ def __init__(self, client): self.tags_api = TagsAPI(self) self.customers_api = CustomersAPI(self) self.registers_api = RegistersAPI(self) + self.workorders_api = WorkordersAPI(self) + self.vendors_api = VendorsAPI(self) self.account_id = self.account.get_account_id() @@ -126,6 +130,14 @@ def employees(self): @property def orders(self): return None + + @property + def vendors(self): + return self.vendors_api + + @property + def workorders(self): + return self.workorders_api class LazyLookupList: @@ -157,4 +169,6 @@ def __len__(self): from .accounts import AccountsAPI from .tags import TagsAPI from .customers import CustomersAPI -from .registers import RegistersAPI \ No newline at end of file +from .registers import RegistersAPI +from .workorders import WorkordersAPI +from .vendors import VendorsAPI \ No newline at end of file diff --git a/lightspeed_api/api/__pycache__/__init__.cpython-39.pyc b/lightspeed_api/api/__pycache__/__init__.cpython-39.pyc index d6a5f80..36b376d 100644 Binary files a/lightspeed_api/api/__pycache__/__init__.cpython-39.pyc and b/lightspeed_api/api/__pycache__/__init__.cpython-39.pyc differ diff --git a/lightspeed_api/api/__pycache__/accounts.cpython-39.pyc b/lightspeed_api/api/__pycache__/accounts.cpython-39.pyc index 15e1bfa..e0c8223 100644 Binary files a/lightspeed_api/api/__pycache__/accounts.cpython-39.pyc and b/lightspeed_api/api/__pycache__/accounts.cpython-39.pyc differ diff --git a/lightspeed_api/api/__pycache__/customers.cpython-39.pyc b/lightspeed_api/api/__pycache__/customers.cpython-39.pyc index 2562f11..afeaeba 100644 Binary files a/lightspeed_api/api/__pycache__/customers.cpython-39.pyc and b/lightspeed_api/api/__pycache__/customers.cpython-39.pyc differ diff --git a/lightspeed_api/api/__pycache__/items.cpython-39.pyc b/lightspeed_api/api/__pycache__/items.cpython-39.pyc index 7b0172d..dab7ad5 100644 Binary files a/lightspeed_api/api/__pycache__/items.cpython-39.pyc and b/lightspeed_api/api/__pycache__/items.cpython-39.pyc differ diff --git a/lightspeed_api/api/__pycache__/sales.cpython-39.pyc b/lightspeed_api/api/__pycache__/sales.cpython-39.pyc index 5ecb746..00638c0 100644 Binary files a/lightspeed_api/api/__pycache__/sales.cpython-39.pyc and b/lightspeed_api/api/__pycache__/sales.cpython-39.pyc differ diff --git a/lightspeed_api/api/__pycache__/tags.cpython-39.pyc b/lightspeed_api/api/__pycache__/tags.cpython-39.pyc index 6a4e251..cb95bf0 100644 Binary files a/lightspeed_api/api/__pycache__/tags.cpython-39.pyc and b/lightspeed_api/api/__pycache__/tags.cpython-39.pyc differ diff --git a/lightspeed_api/api/customers.py b/lightspeed_api/api/customers.py index 882d9a4..8af6838 100644 --- a/lightspeed_api/api/customers.py +++ b/lightspeed_api/api/customers.py @@ -19,14 +19,14 @@ class CustomersAPI(BaseAPI): } } - def get_customer(self, CustomerID, preload_relations=[], raw=False): - url = f'Customer/{CustomerID}.json' + def get_customer(self, id, preload_relations=[], raw=False): + url = f'Customer/{id}.json' return self._get_wrapper(url, raw=raw, preload_relations=preload_relations, object_class=Customer) - def get_customer_custom_field(self, CustomFieldID, raw=False): - url = f'Customer/CustomField/{CustomFieldID}.json' + def get_customer_custom_field(self, id, preload_relations=[], raw=False): + url = f'Customer/CustomField/{id}.json' return self._get_wrapper(url, raw=raw, object_class=CustomerCustomField, object_field='CustomField') - def get_customer_type(self, CustomerTypeID, raw=False): - url = f'CustomerType/{CustomerTypeID}.json' + def get_customer_type(self, id, preload_relations=[], raw=False): + url = f'CustomerType/{id}.json' return self._get_wrapper(url, raw=raw, object_class=CustomerType) \ No newline at end of file diff --git a/lightspeed_api/api/sales.py b/lightspeed_api/api/sales.py index b381fae..ece3270 100644 --- a/lightspeed_api/api/sales.py +++ b/lightspeed_api/api/sales.py @@ -1,7 +1,7 @@ import json from . import BaseAPI -from ..models.sale import Sale, SaleLineItem +from ..models.sale import Sale, SaleLine class SalesAPI(BaseAPI): @@ -36,7 +36,7 @@ def get_all_from_sale(self, SaleID): return_list = [] for obj in data['SaleLine']: - return_list.append(self._unwrap_sales_object(SaleLineItem, obj)) + return_list.append(self._unwrap_sales_object(SaleLine, obj)) return return_list diff --git a/lightspeed_api/api/vendors.py b/lightspeed_api/api/vendors.py new file mode 100644 index 0000000..e7fef0c --- /dev/null +++ b/lightspeed_api/api/vendors.py @@ -0,0 +1,16 @@ + +from . import BaseAPI +from ..models.vendor import * + + +class VendorsAPI(BaseAPI): + _all_methods = { + "": { + "url": "Vendor.json", + "class": Vendor + } + } + + def get_vendor(self, id, preload_relations=[], raw=False): + url = f'Vendor/{id}.json' + return self._get_wrapper(url, raw=raw, preload_relations=preload_relations, object_class=Vendor) diff --git a/lightspeed_api/api/workorders.py b/lightspeed_api/api/workorders.py new file mode 100644 index 0000000..b3c24c1 --- /dev/null +++ b/lightspeed_api/api/workorders.py @@ -0,0 +1,40 @@ + +from . import BaseAPI +from ..models.workorder import * + + +class WorkordersAPI(BaseAPI): + _all_methods = { + "": { + "url": "Workorder.json", + "class": Workorder + }, + "items": { + "url": "WorkorderItem.json", + "class": WorkorderItem + }, + "lines": { + "url": "WorkorderLine.json", + "class": WorkorderLine + }, + "statuses": { + "url": "WorkorderStatus.json", + "class": WorkorderStatus + } + } + + def get_workorder(self, id, preload_relations=[], raw=False): + url = f'Workorder/{id}.json' + return self._get_wrapper(url, raw=raw, preload_relations=preload_relations, object_class=Workorder) + + def get_workorder_item(self, id, preload_relations=[], raw=False): + url = f'WorkorderItem/{id}.json' + return self._get_wrapper(url, raw=raw, object_class=WorkorderItem) #, object_field='CustomField') + + def get_workorder_line(self, id, preload_relations=[], raw=False): + url = f'WorkorderLine/{id}.json' + return self._get_wrapper(url, raw=raw, object_class=WorkorderLine) + + def get_workorder_status(self, id, preload_relations=[], raw=False): + url = f'WorkorderStatus/{id}.json' + return self._get_wrapper(url, raw=raw, object_class=WorkorderStatus) \ No newline at end of file diff --git a/lightspeed_api/client.py b/lightspeed_api/client.py index 76025dc..aadd763 100644 --- a/lightspeed_api/client.py +++ b/lightspeed_api/client.py @@ -60,7 +60,6 @@ def get_authorization_token(self, code): r = s.post(self.token_url, data=payload) json = r.json() - print(json) self.bearer_token = json["access_token"] self.session.headers.update({'Authorization': 'Bearer ' + self.bearer_token}) @@ -75,7 +74,6 @@ def get_token(self): :return: """ if datetime.datetime.now() > self.token_expire_time: - s = requests.Session() try: diff --git a/lightspeed_api/models/__init__.py b/lightspeed_api/models/__init__.py index e7c43f3..36f8075 100644 --- a/lightspeed_api/models/__init__.py +++ b/lightspeed_api/models/__init__.py @@ -6,6 +6,14 @@ # Lazy-loaded API_MODULES = None +API_MODELS = None + + +def _get_model_class(class_name): + global API_MODELS + if not API_MODELS: + API_MODELS = __import__('lightspeed_api').models + return getattr(API_MODELS, class_name) class BaseObject: @@ -27,10 +35,14 @@ def __init__(self, obj=None, api=None): if _id == 0: data = None else: - # TODO: figure out how this is going to work, want it to replace the value with the object when done. data = LazyLookupObject(_id, api, _class, self, term) else: - data = convert_to_type(ls_info['type'], obj[ls_info['ls_field']]) + obj_data = BaseObject._parse_field_parts(ls_info['ls_field'], obj, True) + if obj_data is None: + raise KeyError() # triggers the relationships part below + if 'ls_secondary_field' in ls_info: + obj_data = obj_data[ls_info['ls_secondary_field']] + data = convert_to_type(ls_info['type'], obj_data) if 'convert_class' in ls_info: data = ls_info['convert_class'](data) @@ -45,10 +57,10 @@ def __init__(self, obj=None, api=None): _data_list.append(ls_info['convert_class'](d)) data_list = _data_list - if data_list: - setattr(self, term, data_list) - else: + if not data_list and "relationships" in ls_info: setattr(self, term, LazyLookupAttributes(self.id, api, self._get_function, ls_info)) + else: + setattr(self, term, data_list) except KeyError as ex: if not ('optional' in ls_info and ls_info['optional']): raise ex @@ -64,19 +76,19 @@ def __init__(self, obj=None, api=None): # Only dealing with combined fields now if "combine" not in ls_info: continue - + values = [] for attr in ls_info['combine']: values.append(getattr(self, attr)) - + setattr(self, term, " ".join(values)) - + # Run the "cleanup" function if specified. # Used to make some last-minute queries/adjustments if needed. _cleanup = getattr(self, 'cleanup', None) if _cleanup and callable(_cleanup): _cleanup() - + else: for term in self._object_attributes: ls_info = self._object_attributes[term] @@ -84,36 +96,43 @@ def __init__(self, obj=None, api=None): if "multifield" in ls_info and ls_info['multifield']: data = [] - + data = ls_info.get('default', data) setattr(self, term, data) @staticmethod - def _parse_multifield(ls_info, obj, api): - data_list = [] - parts = ls_info['ls_field'].split('.') + def _parse_field_parts(ls_field, obj, notlist=False): + parts = ls_field.split('.') downstream_obj = obj for p in parts: # This happens when there is no data - a list/dict becomes an empty string. # If so, we'll exit out and assume there is no data. if type(downstream_obj) == str or p not in downstream_obj: - downstream_obj = [] + downstream_obj = [] if not notlist else None break downstream_obj = downstream_obj[p] + return downstream_obj + + @staticmethod + def _parse_multifield(ls_info, obj, api): + data_list = [] + downstream_obj = BaseObject._parse_field_parts(ls_info['ls_field'], obj) + second_field = None if 'ls_secondary_field' in ls_info: second_field = ls_info['ls_secondary_field'] - + # Only a single element in the "list" if type(downstream_obj) == dict: downstream_obj = [downstream_obj] - + for item in downstream_obj: if second_field: data = convert_to_type(ls_info['type'], item[second_field]) - elif issubclass(ls_info['type'], BaseObject): - data = ls_info['type'](item, api) + elif type(ls_info['type']) == str: # Means it's an object/class + _class = _get_model_class(ls_info['type']) + data = _class(item, api) else: raise Exception("Unexpected combination - multifield item, no ls_secondary_field or typecasting") data_list.append(data) @@ -129,7 +148,7 @@ def json(self, dump=True): if "combine" in ls_info: continue - field = ls_info['ls_field'] + field = ls_info.get('ls_field', ls_info.get('ls_field_id', None)) _obj = obj if len(field.split(".")) > 1: fields = field.split(".") @@ -178,24 +197,29 @@ def set_api(self, api): self.api = api def save(self): - print(self.json()) if self.id: - url = getattr(self, '_update_url') + url = getattr(self, '_update_url', None) if url: self.api.request('PUT', url % self.id, self.json()) else: - raise Exception('No _update_url attribute associated to this class - failing!') + raise Exception("Unable to save changes of this object type - API doesn't handle it.") else: - url = getattr(self, '_create_url') + url = getattr(self, '_create_url', None) if url: response = self.api.request('POST', url, self.json()) else: - raise Exception('No _create_url attribute associated to this class - failing!') + raise Exception("Unable to create objects of this type - API doesn't handle it.") + + if not response: + raise Exception("No object returned from Lightspeed - invalid object creation attempted.") response.pop('@attributes') key = list(response.keys())[0] self.id = response[key][self._object_attributes['id']['ls_field']] + def delete(self): + raise Exception("Cannot delete this object type - API doesn't handle it.") + def __eq__(self, item): if type(item) in [type(self), LazyLookupObject]: if 'id' not in self._object_attributes or not getattr(item, 'id', None): @@ -209,7 +233,7 @@ class LazyLookupObject: def __init__(self, id, client, class_obj, parent_obj, parent_field): self.id = id self._client = client - self._class_obj = class_obj + self._class_obj = _get_model_class(class_obj) self._parent_obj = parent_obj self._parent_field = parent_field self._was_loaded = False @@ -237,7 +261,7 @@ def __getattr__(self, attr): if attr != '_was_loaded': if '_was_loaded' in self.__dict__ and not self.__dict__['_was_loaded']: self._load() - return getattr(self._parent_obj, attr) + return getattr(getattr(self._parent_obj, self._parent_field), attr) return self.__dict__[attr] @@ -245,7 +269,7 @@ def __setattr__(self, attr, val): if attr != '_was_loaded': if '_was_loaded' in self.__dict__ and not self.__dict__['_was_loaded']: self._load() - return setattr(self._parent_obj, attr, val) + return setattr(getattr(self._parent_obj, self._parent_field), attr, val) return super().__setattr__(attr, val) @@ -333,6 +357,7 @@ def __eq__(self, item): # Done after declaring the above class from .catalog import * from .category import * +from .contact import * from .credit_account import * from .credit_card import * from .customer import * @@ -357,4 +382,5 @@ def __eq__(self, item): from .shop import * from .tag import * from .tax import * +from .vendor import * from .workorder import * diff --git a/lightspeed_api/models/__pycache__/__init__.cpython-39.pyc b/lightspeed_api/models/__pycache__/__init__.cpython-39.pyc index 2bec549..8b299e1 100644 Binary files a/lightspeed_api/models/__pycache__/__init__.cpython-39.pyc and b/lightspeed_api/models/__pycache__/__init__.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/catalog.cpython-39.pyc b/lightspeed_api/models/__pycache__/catalog.cpython-39.pyc index 1972dc7..85b5964 100644 Binary files a/lightspeed_api/models/__pycache__/catalog.cpython-39.pyc and b/lightspeed_api/models/__pycache__/catalog.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/category.cpython-39.pyc b/lightspeed_api/models/__pycache__/category.cpython-39.pyc index 4a4ee0d..1f0c97c 100644 Binary files a/lightspeed_api/models/__pycache__/category.cpython-39.pyc and b/lightspeed_api/models/__pycache__/category.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/credit_account.cpython-39.pyc b/lightspeed_api/models/__pycache__/credit_account.cpython-39.pyc index 24523bd..4837806 100644 Binary files a/lightspeed_api/models/__pycache__/credit_account.cpython-39.pyc and b/lightspeed_api/models/__pycache__/credit_account.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/credit_card.cpython-39.pyc b/lightspeed_api/models/__pycache__/credit_card.cpython-39.pyc index 528e8eb..afe2b8d 100644 Binary files a/lightspeed_api/models/__pycache__/credit_card.cpython-39.pyc and b/lightspeed_api/models/__pycache__/credit_card.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/customer.cpython-39.pyc b/lightspeed_api/models/__pycache__/customer.cpython-39.pyc index f53a4a1..e164f1c 100644 Binary files a/lightspeed_api/models/__pycache__/customer.cpython-39.pyc and b/lightspeed_api/models/__pycache__/customer.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/discount.cpython-39.pyc b/lightspeed_api/models/__pycache__/discount.cpython-39.pyc index fc200e3..87d6988 100644 Binary files a/lightspeed_api/models/__pycache__/discount.cpython-39.pyc and b/lightspeed_api/models/__pycache__/discount.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/employee.cpython-39.pyc b/lightspeed_api/models/__pycache__/employee.cpython-39.pyc index 8ba6e25..148f2f4 100644 Binary files a/lightspeed_api/models/__pycache__/employee.cpython-39.pyc and b/lightspeed_api/models/__pycache__/employee.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/image.cpython-39.pyc b/lightspeed_api/models/__pycache__/image.cpython-39.pyc index 70a975f..022d320 100644 Binary files a/lightspeed_api/models/__pycache__/image.cpython-39.pyc and b/lightspeed_api/models/__pycache__/image.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/industry.cpython-39.pyc b/lightspeed_api/models/__pycache__/industry.cpython-39.pyc index ca5253c..dcc9109 100644 Binary files a/lightspeed_api/models/__pycache__/industry.cpython-39.pyc and b/lightspeed_api/models/__pycache__/industry.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/inventory.cpython-39.pyc b/lightspeed_api/models/__pycache__/inventory.cpython-39.pyc index 23565c6..cb09244 100644 Binary files a/lightspeed_api/models/__pycache__/inventory.cpython-39.pyc and b/lightspeed_api/models/__pycache__/inventory.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/item.cpython-39.pyc b/lightspeed_api/models/__pycache__/item.cpython-39.pyc index f38c33d..a357a35 100644 Binary files a/lightspeed_api/models/__pycache__/item.cpython-39.pyc and b/lightspeed_api/models/__pycache__/item.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/locale.cpython-39.pyc b/lightspeed_api/models/__pycache__/locale.cpython-39.pyc index d0d7689..8ffe4b8 100644 Binary files a/lightspeed_api/models/__pycache__/locale.cpython-39.pyc and b/lightspeed_api/models/__pycache__/locale.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/manufacturer.cpython-39.pyc b/lightspeed_api/models/__pycache__/manufacturer.cpython-39.pyc index 2a9c7d1..d0f4f5c 100644 Binary files a/lightspeed_api/models/__pycache__/manufacturer.cpython-39.pyc and b/lightspeed_api/models/__pycache__/manufacturer.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/note.cpython-39.pyc b/lightspeed_api/models/__pycache__/note.cpython-39.pyc index 54e9c7e..858f93c 100644 Binary files a/lightspeed_api/models/__pycache__/note.cpython-39.pyc and b/lightspeed_api/models/__pycache__/note.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/options.cpython-39.pyc b/lightspeed_api/models/__pycache__/options.cpython-39.pyc index a227109..d9f829d 100644 Binary files a/lightspeed_api/models/__pycache__/options.cpython-39.pyc and b/lightspeed_api/models/__pycache__/options.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/order.cpython-39.pyc b/lightspeed_api/models/__pycache__/order.cpython-39.pyc index 8ca7216..27a33e5 100644 Binary files a/lightspeed_api/models/__pycache__/order.cpython-39.pyc and b/lightspeed_api/models/__pycache__/order.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/payment_type.cpython-39.pyc b/lightspeed_api/models/__pycache__/payment_type.cpython-39.pyc index 2f7c041..ebfaa8e 100644 Binary files a/lightspeed_api/models/__pycache__/payment_type.cpython-39.pyc and b/lightspeed_api/models/__pycache__/payment_type.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/price_level.cpython-39.pyc b/lightspeed_api/models/__pycache__/price_level.cpython-39.pyc index 5553292..d847f0a 100644 Binary files a/lightspeed_api/models/__pycache__/price_level.cpython-39.pyc and b/lightspeed_api/models/__pycache__/price_level.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/register.cpython-39.pyc b/lightspeed_api/models/__pycache__/register.cpython-39.pyc index c53dadb..b6ee097 100644 Binary files a/lightspeed_api/models/__pycache__/register.cpython-39.pyc and b/lightspeed_api/models/__pycache__/register.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/sale.cpython-39.pyc b/lightspeed_api/models/__pycache__/sale.cpython-39.pyc index f828ae9..3592ec0 100644 Binary files a/lightspeed_api/models/__pycache__/sale.cpython-39.pyc and b/lightspeed_api/models/__pycache__/sale.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/serialized.cpython-39.pyc b/lightspeed_api/models/__pycache__/serialized.cpython-39.pyc index 2117571..6f5b039 100644 Binary files a/lightspeed_api/models/__pycache__/serialized.cpython-39.pyc and b/lightspeed_api/models/__pycache__/serialized.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/session.cpython-39.pyc b/lightspeed_api/models/__pycache__/session.cpython-39.pyc index 473d0ea..f7bd913 100644 Binary files a/lightspeed_api/models/__pycache__/session.cpython-39.pyc and b/lightspeed_api/models/__pycache__/session.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/shipping.cpython-39.pyc b/lightspeed_api/models/__pycache__/shipping.cpython-39.pyc index ca66ebc..3c82a8b 100644 Binary files a/lightspeed_api/models/__pycache__/shipping.cpython-39.pyc and b/lightspeed_api/models/__pycache__/shipping.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/shop.cpython-39.pyc b/lightspeed_api/models/__pycache__/shop.cpython-39.pyc index 4e9675c..4d2f1f9 100644 Binary files a/lightspeed_api/models/__pycache__/shop.cpython-39.pyc and b/lightspeed_api/models/__pycache__/shop.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/tag.cpython-39.pyc b/lightspeed_api/models/__pycache__/tag.cpython-39.pyc index d28d5a7..a914f96 100644 Binary files a/lightspeed_api/models/__pycache__/tag.cpython-39.pyc and b/lightspeed_api/models/__pycache__/tag.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/tax.cpython-39.pyc b/lightspeed_api/models/__pycache__/tax.cpython-39.pyc index 7eaed1f..5726225 100644 Binary files a/lightspeed_api/models/__pycache__/tax.cpython-39.pyc and b/lightspeed_api/models/__pycache__/tax.cpython-39.pyc differ diff --git a/lightspeed_api/models/__pycache__/workorder.cpython-39.pyc b/lightspeed_api/models/__pycache__/workorder.cpython-39.pyc index 1646c6e..c3bf2b7 100644 Binary files a/lightspeed_api/models/__pycache__/workorder.cpython-39.pyc and b/lightspeed_api/models/__pycache__/workorder.cpython-39.pyc differ diff --git a/lightspeed_api/models/catalog.py b/lightspeed_api/models/catalog.py index 0db6926..65a42d5 100644 --- a/lightspeed_api/models/catalog.py +++ b/lightspeed_api/models/catalog.py @@ -1,13 +1,58 @@ +from datetime import datetime, date + from . import BaseObject -class CatalogVendorItem(BaseObject): + +class CatalogVendor(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "catalogVendorID"}, + "name": {"type": str, "ls_field": "name"}, + "last_updated": {"type": datetime, "ls_field": "lastUpdate"}, } -class Vendor(BaseObject): +class CatalogVendorAvailability(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, - } \ No newline at end of file + "id": {"type": int, "ls_field": "catalogVendorID"}, + } + + +class CatalogVendorItem(BaseObject): + _object_attributes = { + "id": {"type": int, "ls_field": "catalogVendorItemID"}, + "vendor_number": {"type": str, "ls_field": "vendorNumber"}, + "description": {"type": str, "ls_field": "description"}, + "category": {"type": str, "ls_field": "category"}, + "model": {"type": str, "ls_field": "model"}, + "unit_count": {"type": int, "ls_field": "retailUnit"}, + "units": {"type": str, "ls_field": "unitOfMeasurement"}, + "cost": {"type": float, "ls_field": "cost"}, + "cost_discount_1": {"type": float, "ls_field": "costLevel2"}, + "cost_discount_2": {"type": float, "ls_field": "costLevel3"}, + "cost_discount_3": {"type": float, "ls_field": "costLevel4"}, + "msrp": {"type": float, "ls_field": "msrp"}, + "break_count_1": {"type": int, "ls_field": "breakQty"}, + "break_price_1": {"type": float, "ls_field": "breakPrice"}, + "break_count_2": {"type": int, "ls_field": "breakQty2"}, + "break_price_2": {"type": float, "ls_field": "breakPrice2"}, + "break_count_3": {"type": int, "ls_field": "breakQty3"}, + "break_price_3": {"type": float, "ls_field": "breakPrice3"}, + "manufacturer_number": {"type": str, "ls_field": "manufacturerNumber"}, + "brand": {"type": str, "ls_field": "brand"}, + "last_price_change": {"type": date, "ls_field": "lastPriceChange"}, + "upc": {"type": str, "ls_field": "upc"}, + "ean": {"type": str, "ls_field": "ean"}, + "alternate_ean": {"type": str, "ls_field": "ean2"}, + "status": {"type": str, "ls_field": "status"}, + "year": {"type": str, "ls_field": "year"}, + "replacement_item": {"type": str, "ls_field": "replacement"}, + "replacement_description": {"type": str, "ls_field": "replacementDescription"}, + "color": {"type": str, "ls_field": "colorName"}, + "size": {"type": str, "ls_field": "sizeName"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "last_vendor_quantity": {"type": int, "ls_field": "lastQoh"}, + "vendor": {"type": CatalogVendor, "ls_field": "CatalogVendor", "ls_field_id": "catalogVendorID", "relationships": ["CatalogVendor"]}, + "vendor_availability": {"type": CatalogVendorAvailability, "multifield": True, "ls_field": "CatalogVendorAvailability", "relationships": ["CatalogVendor"]}, + } + _create_url = 'CatalogVendorItem.json' \ No newline at end of file diff --git a/lightspeed_api/models/category.py b/lightspeed_api/models/category.py index 625285d..869a7e8 100644 --- a/lightspeed_api/models/category.py +++ b/lightspeed_api/models/category.py @@ -1,7 +1,18 @@ +from datetime import datetime + from . import BaseObject + class Category(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "categoryID"}, + "name": {"type": int, "ls_field": "name"}, + "created_time": {"type": int, "ls_field": "createTime"}, + "last_modified_time": {"type": int, "ls_field": "timeStamp"}, + "parent_category": {"type": 'Category', "ls_field": "Parent", "ls_field_id": "parentID", "relationships": ["Parent"]}, + "node_depth": {"type": str, "ls_field": "nodeDepth"}, + "full_path": {"type": str, "ls_field": "fullPathName"}, + "left_node": {"type": int, "ls_field": "leftNode"}, + "right_node": {"type": int, "ls_field": "rightNode"}, } \ No newline at end of file diff --git a/lightspeed_api/models/contact.py b/lightspeed_api/models/contact.py new file mode 100644 index 0000000..38f870a --- /dev/null +++ b/lightspeed_api/models/contact.py @@ -0,0 +1,16 @@ + +from datetime import datetime +from . import BaseObject + + +class Contact(BaseObject): + _object_attributes = { + "is_email_allowed": {"type": bool, "ls_field": "noEmail"}, + "is_phone_allowed": {"type": bool, "ls_field": "noPhone"}, + "is_mail_allowed": {"type": bool, "ls_field": "noMail"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "emails": {"type": 'CustomerEmail', "multifield": True, "ls_field": "Emails.ContactEmail"}, + "phone_numbers": {"type": 'CustomerPhoneNumber', "multifield": True, "ls_field": "Phones.ContactPhone"}, + "addresses": {"type": 'CustomerAddress', "multifield": True, "ls_field": "Addresses.ContactAddress"}, + "website": {"type": str, "ls_field": "Websites.ContactWebsite", "ls_secondary_field": "url", "optional": True}, + } diff --git a/lightspeed_api/models/credit_account.py b/lightspeed_api/models/credit_account.py index ddabde6..b43ede0 100644 --- a/lightspeed_api/models/credit_account.py +++ b/lightspeed_api/models/credit_account.py @@ -1,7 +1,23 @@ +from datetime import datetime + from . import BaseObject + class CreditAccount(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, - } \ No newline at end of file + "id": {"type": int, "ls_field": "creditAccountID"}, + "name": {"type": str, "ls_field": "name"}, + "code": {"type": str, "ls_field": "code"}, + "description": {"type": str, "ls_field": "description"}, + "is_giftcard": {"type": bool, "ls_field": "giftCard"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "customer": {"type": 'Customer', "ls_field": "Contact", "ls_field_id": "customerID", "relationships": ["Contact"]}, # TODO: might actually be a "Contact" object from a customer + "withdrawals": {"type": 'SalePayment', "multifield": True, "ls_field": "WithdrawalPayments.Payment", "relationships": ["WithdrawalPayments"]}, + "last_modified_time": {"type": int, "ls_field": "timeStamp"}, + "balance": {"type": float, "ls_field": "balance"}, + } + _get_function = "CreditAccountsAPI.get_customer" + _update_url = 'CreditAccount/%s.json' + _create_url = 'CreditAccount.json' + _delete_url = 'CreditAccount/%s.json' \ No newline at end of file diff --git a/lightspeed_api/models/credit_card.py b/lightspeed_api/models/credit_card.py index f578495..1b6ebbb 100644 --- a/lightspeed_api/models/credit_card.py +++ b/lightspeed_api/models/credit_card.py @@ -1,7 +1,49 @@ +from datetime import datetime from . import BaseObject + class CreditCardCharge(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "ccChargeID"}, + "transaction_id": {"type": str, "ls_field": "gatewayTransID"}, + "card_last_4_digits": {"type": str, "ls_field": "xnum"}, + "response": {"type": str, "ls_field": "response"}, + "is_voided": {"type": bool, "ls_field": "voided"}, + "refunded_amount": {"type": float, "ls_field": "refunded"}, + "charged_amount": {"type": float, "ls_field": "amount"}, + "card_expiry": {"type": str, "ls_field": "exp"}, + "is_auth_only": {"type": bool, "ls_field": "authOnly"}, + "authorization_code": {"type": str, "ls_field": "authCode"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "is_declined": {"type": bool, "ls_field": "declined"}, + "entry_method": {"type": str, "ls_field": "entryMethod"}, + "cardholder_name": {"type": str, "ls_field": "cardholderName"}, + "communication_key": {"type": str, "ls_field": "communicationKey"}, + "sale": {"type": 'Sale', "ls_field_id": "saleID"}, + "payments": {"type": 'SalePayment', "multifield": True, "ls_field": "SalePayments", "relationships": ["SalePayments"]}, + } + + +class CreditCardGateway(BaseObject): + _object_attributes = { + "id": {"type": int, "ls_field": "ccGatewayID"}, + "login": {"type": str, "ls_field": "login"}, + "market_type": {"type": str, "ls_field": "marketType"}, + "transaction_key": {"type": str, "ls_field": "transKey"}, + "device_type": {"type": int, "ls_field": "deviceType"}, + "hash": {"type": str, "ls_field": "hashValue"}, + "is_enabled": {"type": bool, "ls_field": "enabled"}, + "is_test_mode_enabled": {"type": bool, "ls_field": "testMode"}, + "gateway_name": {"type": str, "ls_field": "gateway"}, + "account_number": {"type": str, "ls_field": "accountNum"}, + "terminal_id": {"type": str, "ls_field": "terminalNum"}, + "is_credit_card_charges_allowed": {"type": bool, "ls_field": "allowCredits"}, + "other_credentials_1": {"type": str, "ls_field": "otherCredentials1"}, + "other_credentials_2": {"type": str, "ls_field": "otherCredentials2"}, + "visa_payment_type": {"type": 'PaymentType', "ls_field_id": "visaPaymentTypeID"}, + "mastercard_payment_type": {"type": 'PaymentType', "ls_field_id": "masterPaymentTypeID"}, + "discover_payment_type": {"type": 'PaymentType', "ls_field_id": "discoverPaymentTypeID"}, + "debit_payment_type": {"type": 'PaymentType', "ls_field_id": "debitPaymentTypeID"}, + "american_payment_type": {"type": 'PaymentType', "ls_field_id": "americanPaymentTypeID"}, } \ No newline at end of file diff --git a/lightspeed_api/models/customer.py b/lightspeed_api/models/customer.py index f501127..db5e3c8 100644 --- a/lightspeed_api/models/customer.py +++ b/lightspeed_api/models/customer.py @@ -3,11 +3,6 @@ from enum import Enum from . import BaseObject -from .credit_account import CreditAccount -from .discount import Discount -from .note import Note -from .tag import Tag -from .tax import TaxCategory from ..api import ClientAPI @@ -16,8 +11,8 @@ class CustomerType(BaseObject): _object_attributes = { "id": {"type": int, "ls_field": "customerTypeID"}, "name": {"type": str, "ls_field": "name"}, - "tax_category": {"type": TaxCategory, "ls_field": "TaxCategory", "ls_field_id": "taxCategoryID", "relationships": ["TaxCategory"]}, # TODO: there are TaxCategory.TaxCategoryClasses and TaxCategory.TaxCategoryClasses.TaxClass relationships for this item too - should we load them all? - "discount": {"type": Discount, "ls_field": "Discount", "ls_field_id": "discountID", "relationships": ["Discount"]} + "tax_category": {"type": 'TaxCategory', "ls_field": "TaxCategory", "ls_field_id": "taxCategoryID", "relationships": ["TaxCategory"]}, # TODO: there are TaxCategory.TaxCategoryClasses and TaxCategory.TaxCategoryClasses.TaxClass relationships for this item too - should we load them all? + "discount": {"type": 'Discount', "ls_field": "Discount", "ls_field_id": "discountID", "relationships": ["Discount"]} } _get_function = "CustomersAPI.get_customer_type" @@ -38,7 +33,8 @@ class CustomerCustomField(BaseObject): class CustomerCustomFieldValue(BaseObject): _object_attributes = { "id": {"type": int, "ls_field": "customFieldValueID"}, - "custom_field": {"type": CustomerCustomField, "ls_field_id": "customFieldID"}, + "custom_field": {"type": 'CustomerCustomField', "ls_field_id": "customFieldID"}, + #"is_deleted": {"type": bool, "ls_field": "deleted"}, "name": {"type": str, "ls_field": "name"}, "type": {"type": str, "ls_field": "type"}, # TODO: do we want to convert this to a Python type? "value": {"type": str, "ls_field": "value"}, # TODO: do we want to convert this to its Python type? @@ -47,7 +43,11 @@ class CustomerCustomFieldValue(BaseObject): class CustomerCustomFieldChoice(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "customFieldChoiceID"}, + "name": {"type": str, "ls_field": "name"}, + "value": {"type": str, "ls_field": "value"}, + "is_deletable": {"type": bool, "ls_field": "canBeDeleted"}, + "custom_field": {"type": 'CustomerCustomField', "ls_field_id": "customFieldID"}, } @@ -125,17 +125,14 @@ class Customer(BaseObject): "vat_number": {"type": str, "ls_field": "vatNumber"}, "created_time": {"type": datetime, "ls_field": "createTime"}, "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, - "emails": {"type": CustomerEmail, "multifield": True, "ls_field": "Contact.Emails.ContactEmail", "relationships": ["Contact"]}, - "phone_numbers": {"type": CustomerPhoneNumber, "multifield": True, "ls_field": "Contact.Phones.ContactPhone", "relationships": ["Contact"]}, - "addresses": {"type": CustomerAddress, "multifield": True, "ls_field": "Contact.Addresses.ContactAddress", "relationships": ["Contact"]}, - "website": {"type": str, "ls_field": "Contact.Websites.ContactWebsite", "ls_secondary_field": "url", "optional": True, "relationships": ["Contact"]}, - "tags": {"type": Tag, "multifield": True, "ls_field": "Tags.Tag", "relationships": ["Tags"]}, - "credit_account": {"type": CreditAccount, "ls_field": "CreditAccount", "ls_field_id": "creditAccountID", "relationships": ["CreditAccount"]}, - "type": {"type": CustomerType, "ls_field": "CustomerType", "ls_field_id": "customerTypeID", "relationships": ["CustomerType"]}, - "discount": {"type": Discount, "ls_field": "Discount", "ls_field_id": "discountID", "relationships": ["Discount"]}, - "tax_category": {"type": TaxCategory, "ls_field": "TaxCategory", "ls_field_id": "taxCategoryID", "relationships": ["TaxCategory"]}, - "note": {"type": Note, "ls_field": "Note", "relationships": ["Note"], 'optional': True}, - "custom_fields": {"type": CustomerCustomFieldValue, "multifield": True, "ls_field": "CustomFieldValues.CustomFieldValue", "relationships": ["CustomFieldValues", "CustomFieldValues.value"]}, + "contact": {"type": 'Contact', 'optional': True, "ls_field": "Contact", "relationships": ["Contact"]}, + "tags": {"type": 'Tag', "multifield": True, "ls_field": "Tags.Tag", "relationships": ["Tags"]}, + "credit_account": {"type": 'CreditAccount', "ls_field": "CreditAccount", "ls_field_id": "creditAccountID", "relationships": ["CreditAccount"]}, + "type": {"type": 'CustomerType', "ls_field": "CustomerType", "ls_field_id": "customerTypeID", "relationships": ["CustomerType"]}, + "discount": {"type": 'Discount', "ls_field": "Discount", "ls_field_id": "discountID", "relationships": ["Discount"]}, + "tax_category": {"type": 'TaxCategory', "ls_field": "TaxCategory", "ls_field_id": "taxCategoryID", "relationships": ["TaxCategory"]}, + "note": {"type": 'Note', "ls_field": "Note", "relationships": ["Note"], 'optional': True}, + "custom_fields": {"type": 'CustomerCustomFieldValue', "multifield": True, "ls_field": "CustomFieldValues.CustomFieldValue", "relationships": ["CustomFieldValues", "CustomFieldValues.value"]}, } _get_function = "CustomersAPI.get_customer" _update_url = 'Customer/%s.json' diff --git a/lightspeed_api/models/discount.py b/lightspeed_api/models/discount.py index 96afe27..1a0cfa6 100644 --- a/lightspeed_api/models/discount.py +++ b/lightspeed_api/models/discount.py @@ -1,7 +1,19 @@ +from datetime import datetime + from . import BaseObject + class Discount(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, - } \ No newline at end of file + "id": {"type": int, "ls_field": "discountID"}, + "name": {"type": str, "ls_field": "name"}, + "amount": {"type": float, "ls_field": "discountAmount"}, + "percentage": {"type": float, "ls_field": "discountPercent"}, + "is_customer_required": {"type": bool, "ls_field": "requireCustomer"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + } + _update_url = 'Discount/%s.json' + _create_url = 'Discount.json' + _delete_url = 'Discount/%s.json' \ No newline at end of file diff --git a/lightspeed_api/models/employee.py b/lightspeed_api/models/employee.py index 508fcc2..30c23ce 100644 --- a/lightspeed_api/models/employee.py +++ b/lightspeed_api/models/employee.py @@ -1,13 +1,54 @@ +from datetime import datetime + from . import BaseObject + +class EmployeeRight(BaseObject): + _object_attributes = { + "id": {"type": int, "ls_field": "employeeRightID"}, + "name": {"type": str, "ls_field": "name"}, + } + + +class EmployeeRole(BaseObject): + _object_attributes = { + "id": {"type": int, "ls_field": "employeeRoleID"}, + "name": {"type": str, "ls_field": "name"}, + "emails": {"type": 'EmployeeRight', "multifield": True, "ls_field": "EmployeeRights"}, + } + + class Employee(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "employeeID"}, + "first_name": {"type": str, "ls_field": "firstName"}, + "last_name": {"type": str, "ls_field": "lastName"}, + "is_locked_out": {"type": bool, "ls_field": "lockOut"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "customer": {"type": 'Customer', "ls_field": "Contact", "ls_field_id": "contactID", "relationships": ["Contact"]}, # TODO: might actually be a "Contact" object from a customer + "currently_clocked_in_hours": {"type": 'EmployeeHours', "ls_field": "clockInEmployeeHoursID"}, + "role": {"type": 'EmployeeRole', "ls_field": "Contact", "ls_field_id": "contactID", "relationships": ["EmployeeRole", "EmployeeRole.EmployeeRights"]}, + "rights": {"type": 'EmployeeRight', "multifield": True, "ls_field": "EmployeeRights", "relationships": ["EmployeeRights"]}, + "limited_to_shop": {"type": 'Shop', "ls_field_id": "limitToShopID"}, + "last_shop": {"type": 'Shop', "ls_field_id": "lastShopID"}, + "last_sale": {"type": 'Sale', "ls_field_id": "lastSaleID"}, + "last_register": {"type": 'Register', "ls_field_id": "lastRegisterID"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, } + _update_url = 'Employee/%s.json' + _create_url = 'Employee.json' + _delete_url = 'Employee/%s.json' class EmployeeHours(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, - } \ No newline at end of file + "id": {"type": int, "ls_field": "employeeHoursID"}, + "start": {"type": datetime, "ls_field": "checkIn"}, + "end": {"type": datetime, "ls_field": "checkOut"}, + "employee": {"type": 'Employee', "ls_field_id": "employeeID"}, + "shop": {"type": 'Shop', "ls_field_id": "shopID"}, + } + _update_url = 'EmployeeHours/%s.json' + _create_url = 'EmployeeHours.json' + _delete_url = 'EmployeeHours/%s.json' \ No newline at end of file diff --git a/lightspeed_api/models/image.py b/lightspeed_api/models/image.py index dc7935f..3529b1e 100644 --- a/lightspeed_api/models/image.py +++ b/lightspeed_api/models/image.py @@ -3,5 +3,14 @@ class Image(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, - } \ No newline at end of file + "id": {"type": int, "ls_field": "imageID"}, + "description": {"type": str, "ls_field": "description"}, + "filename": {"type": str, "ls_field": "filename"}, + "order": {"type": int, "ls_field": "ordering"}, + "public_image_id": {"type": str, "ls_field": "publicID"}, + "item": {"type": 'Item', "ls_field": "Item", "ls_field_id": "itemID", "relationships": ["Item"]}, + "item_matrix": {"type": 'ItemMatrix', "ls_field": "ItemMatrix", "ls_field_id": "itemMatrixID", "relationships": ["ItemMatrix"]}, + } + + # TODO: build API that can upload images + # TODO: build function that can download images (with settings for height/width/scale/etc.) \ No newline at end of file diff --git a/lightspeed_api/models/industry.py b/lightspeed_api/models/industry.py index f6aea16..507e07f 100644 --- a/lightspeed_api/models/industry.py +++ b/lightspeed_api/models/industry.py @@ -3,5 +3,7 @@ class Industry(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "industryID"}, + "name": {"type": str, "ls_field": "name"}, + "is_enabled": {"type": bool, "ls_field": "enabled"}, } \ No newline at end of file diff --git a/lightspeed_api/models/inventory.py b/lightspeed_api/models/inventory.py index c8ef193..7ab28dd 100644 --- a/lightspeed_api/models/inventory.py +++ b/lightspeed_api/models/inventory.py @@ -1,13 +1,78 @@ +from datetime import datetime + from . import BaseObject class InventoryCount(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "inventoryCountID"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "name": {"type": str, "ls_field": "name"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "shop": {"type": 'Shop', "ls_field_id": "shopID"}, + } + + +class InventoryCountItem(BaseObject): + _object_attributes = { + "id": {"type": int, "ls_field": "inventoryCountItemID"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "quantity": {"type": int, "ls_field": "qty"}, + "inventory": {"type": 'InventoryCount', "ls_field_id": "inventoryCountID"}, + "employee": {"type": 'Employee', "ls_field_id": "employeeID"}, + "item": {"type": 'Item', "ls_field_id": "itemID"}, + } + + +class InventoryCountCalculation(BaseObject): + _object_attributes = { + "id": {"type": int, "ls_field": "inventoryCountCalcID"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "expected_count": {"type": int, "ls_field": "calcQoh"}, + "actual_count": {"type": int, "ls_field": "countedQoh"}, + "inventory": {"type": 'InventoryCount', "ls_field_id": "inventoryCountID"}, + "item": {"type": 'Item', "ls_field_id": "shopID"}, + } + + +class InventoryCountReconciliation(BaseObject): + _object_attributes = { + "id": {"type": int, "ls_field": "inventoryCountReconcileID"}, + "created_time": {"type": datetime, "ls_field": "createTime"}, + "cost_change": {"type": float, "ls_field": "costChange"}, + "count_change": {"type": int, "ls_field": "qohChange"}, + "inventory": {"type": 'InventoryCount', "ls_field_id": "inventoryCountID"}, + "item": {"type": 'Item', "ls_field_id": "shopID"}, } class InventoryTransfer(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "transferID"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "created_time": {"type": datetime, "ls_field": "createTime"}, + "note": {"type": str, "ls_field": "note"}, + "status": {"type": str, "ls_field": "status"}, + "sent_time": {"type": datetime, "ls_field": "sentOn"}, + "needed_by_time": {"type": datetime, "ls_field": "needBy"}, + "created_employee": {"type": 'Employee', "ls_field_id": "sentByEmployeeID", "relationships": ["CreatedByEmployee", "CreatedByEmployee.Contact"]}, + "sending_employee": {"type": 'Employee', "ls_field_id": "sentByEmployeeID", "relationships": ["SentByEmployee", "SentByEmployee.Contact"]}, + "sending_shop": {"type": 'Shop', "ls_field_id": "sendingShopID", "relationships": ["SendingShop", "SendingShop.Contact"]}, + "receiving_shop": {"type": 'Shop', "ls_field_id": "receivingShopID", "relationships": ["ReceivingShop", "ReceivingShop.Contact"]}, + } + +class InventoryTransferItem(BaseObject): + _object_attributes = { + "id": {"type": int, "ls_field": "transferItemID"}, + "count_sending": {"type": int, "ls_field": "toSend"}, + "count_receiving": {"type": int, "ls_field": "toReceive"}, + "currently_sent": {"type": int, "ls_field": "sent"}, + "currently_received": {"type": int, "ls_field": "received"}, + "value_sent": {"type": float, "ls_field": "sentValue"}, + "value_received": {"type": float, "ls_field": "receivedValue"}, + "comment": {"type": str, "ls_field": "comment"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "transfer": {"type": 'InventoryTransfer', "ls_field_id": "transferID"}, + "item": {"type": 'Item', "ls_field_id": "itemID"}, } \ No newline at end of file diff --git a/lightspeed_api/models/item.py b/lightspeed_api/models/item.py index 691ecf7..e84fc73 100644 --- a/lightspeed_api/models/item.py +++ b/lightspeed_api/models/item.py @@ -14,49 +14,25 @@ class Item(BaseObject): "manufacturer_sku": {"type": str, "ls_field": "manufacturerSku"}, "default_cost": {"type": float, "ls_field": "defaultCost"}, "average_cost": {"type": float, "ls_field": "avgCost"}, - "taxed": {"type": bool, "ls_field": "tax"}, - "archived": {"type": bool, "ls_field": "archived"}, - "discountable": {"type": bool, "ls_field": "discountable"}, - "serialized": {"type": bool, "ls_field": "serialized"}, - "added_to_ecom": {"type": bool, "ls_field": "publishToEcom"}, + "is_taxed": {"type": bool, "ls_field": "tax"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "is_discountable": {"type": bool, "ls_field": "discountable"}, + "is_serialized": {"type": bool, "ls_field": "serialized"}, + "is_publishable_to_ecom": {"type": bool, "ls_field": "publishToEcom"}, "type": {"type": str, "ls_field": "systemSku"}, # TODO: handle enum values: default, non_inventory, serialized, box, serialized_assembly, assembly "description": {"type": str, "ls_field": "description"}, "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, "created_time": {"type": datetime, "ls_field": "createTime"}, "tags": {"type": Tag, "multifield": True, "ls_field": "Tags.tag", "relationships": ["TagRelations.Tag"]}, - # TODO: make these map to objects dynamically instead of just an ID - "category_id": {"type": int, "ls_field": "categoryID"}, - "item_matrix_id": {"type": int, "ls_field": "itemMatrixID"}, - "manufacturer_id": {"type": int, "ls_field": "manufacturerID"}, - "tax_class_id": {"type": int, "ls_field": "taxClassID"}, - "default_vendor_id": {"type": int, "ls_field": "defaultVendorID"}, - # TODO: Note field - # TODO: Category, TaxClass, ItemAttributes, Manufacturer, ItemShops, - # ItemComponents, ItemShelfLocations, ItemVendorNums fields - # TODO: CustomFieldValues field + "category": {"type": 'Category', "ls_field": "categoryID", "relationships": ["Category"]}, + "item_matrix": {"type": 'ItemMatrix', "ls_field": "itemMatrixID"}, + "manufacturer": {"type": 'Manufacturer', "ls_field": "manufacturerID", "relationships": ["Manufacturer"]}, + "tax_class": {"type": 'TaxClass', "ls_field": "taxClassID", "relationships": ["TaxClass"]}, + "default_vendor": {"type": "Vendor", "ls_field": "defaultVendorID"}, + # TODO: Images, ItemAttributes, ItemShops, ItemComponents, ItemShelfLocations, ItemVendorNums fields + "note": {"type": 'Note', "ls_field": "Note", "relationships": ["Note"], 'optional': True}, + "custom_fields": {"type": 'ItemCustomFieldValue', "multifield": True, "ls_field": "CustomFieldValues.CustomFieldValue", "relationships": ["CustomFieldValues", "CustomFieldValues.value"]}, } - def __init__(self, obj=None, api=None): - if obj: - # TODO: figure out if fields (like self.name) are just references to fields in the JSON blob or extracted as-is - self.id = obj['itemID'] - self.created_at = obj['createTime'] - self.name = obj['description'] - self.tags = [] - self.json_object = obj - - # TODO: move this + setter into BaseObject - # TODO: setter function should have format function to set up the object (i.e. converting from str to obj)? - @property - def tags(self): - if not self._tags: - self._tags = self.api.items.get_tags_for_item(self.id) - return self._tags # TODO: return list object that allows you to add/delete from the list via just a string (instead of a tag object)? - - @tags.setter - def tags(self, tags): - self._tags = [] - for t in tags: - self._tags.append(Tag({"name": t, "tagID": None})) def get_associated_sales(self, created_at=None): return self.api.sales.get_sales_for_item(self.id, created_at=created_at) @@ -64,26 +40,67 @@ def get_associated_sales(self, created_at=None): def __repr__(self): return self.name + # TODO: get_image function class ItemCustomField(BaseObject): _object_attributes = { "id": {"type": int, "ls_field": "customFieldID"}, + "name": {"type": str, "ls_field": "name"}, + "type": {"type": str, "ls_field": "type"}, + "units": {"type": str, "ls_field": "uom"}, + "decimal_precision": {"type": int, "ls_field": "decimalPrecision"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "default": {"type": str, "ls_field": "default"}, # TODO: may be a mixed type, so this may not work? } -class ItemCustomFieldChoice(BaseObject): +class ItemCustomFieldValue(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "customFieldValueID"}, + "custom_field": {"type": 'ItemCustomField', "ls_field_id": "customFieldID"}, + "is_deleted": {"type": bool, "ls_field": "deleted"}, + "name": {"type": str, "ls_field": "name"}, + "type": {"type": str, "ls_field": "type"}, # TODO: do we want to convert this to a Python type? + "value": {"type": str, "ls_field": "value"}, # TODO: do we want to convert this to its Python type? + } + + +class ItemCustomFieldChoice(BaseObject): + _object_attributes = { # TODO: same as customer? + "id": {"type": int, "ls_field": "customFieldChoiceID"}, + "name": {"type": str, "ls_field": "name"}, + "value": {"type": str, "ls_field": "value"}, + "is_deletable": {"type": bool, "ls_field": "canBeDeleted"}, + "custom_field": {"type": 'ItemCustomField', "ls_field_id": "customFieldID"}, } class ItemAttributeSet(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "itemAttributeSetID"}, + "name": {"type": str, "ls_field": "name"}, + "attribute_1": {"type": str, "ls_field": "attributeName1"}, + "attribute_2": {"type": str, "ls_field": "attributeName2"}, + "attribute_3": {"type": str, "ls_field": "attributeName3"}, } class ItemMatrix(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, - } \ No newline at end of file + "id": {"type": int, "ls_field": "itemMatrixID"}, + "default_cost": {"type": float, "ls_field": "defaultCost"}, + "is_taxed": {"type": bool, "ls_field": "tax"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "is_serialized": {"type": bool, "ls_field": "serialized"}, + "type": {"type": str, "ls_field": "systemSku"}, # TODO: handle enum values: default, non_inventory, serialized, box, serialized_assembly, assembly + "description": {"type": str, "ls_field": "description"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "category": {"type": 'Category', "ls_field": "categoryID", "relationships": ["Category"]}, + "manufacturer": {"type": 'Manufacturer', "ls_field": "manufacturerID", "relationships": ["Manufacturer"]}, + "tax_class": {"type": 'TaxClass', "ls_field": "taxClassID", "relationships": ["TaxClass"]}, + "default_vendor": {"type": "Vendor", "ls_field": "defaultVendorID"}, + "item_attributes": {"type": 'ItemAttributeSet', "ls_field": "itemAttributeSetID", "relationships": ["ItemAttributeSet"]}, + "items": {"type": 'Item', "multifield": True, "ls_field": "Items.Item", "relationships": ["Items"]}, + "custom_fields": {"type": 'ItemCustomFieldValue', "multifield": True, "ls_field": "CustomFieldValues.CustomFieldValue", "relationships": ["CustomFieldValues", "CustomFieldValues.value"]}, + } + # TODO: images attribute \ No newline at end of file diff --git a/lightspeed_api/models/locale.py b/lightspeed_api/models/locale.py index 418e80a..e28d81c 100644 --- a/lightspeed_api/models/locale.py +++ b/lightspeed_api/models/locale.py @@ -1,7 +1,28 @@ from . import BaseObject +class CurrencyDenomination(BaseObject): + _object_attributes = { + "name": {"type": str, "ls_field": "name"}, + "value": {"type": str, "ls_field": "value"}, + "is_significant": {"type": bool, "ls_field": "significant"}, + } + + class Locale(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "name": {"type": str, "ls_field": "name"}, + "country": {"type": str, "ls_field": "country"}, + "states": {"type": str, "ls_field": "states"}, # TODO: formatting of this magically? from list to concat'd string? + "currency_symbol": {"type": str, "ls_field": "currencySymbol"}, + "currency_code": {"type": str, "ls_field": "currencyCode"}, + "currency_precision": {"type": float, "ls_field": "currencyPrecision"}, + "cash_rounding_precision": {"type": float, "ls_field": "cashRoundingPrecision"}, + "is_include_tax_on_labels": {"type": bool, "ls_field": "includeTaxOnLabels"}, + "language_tag": {"type": str, "ls_field": "languageTag"}, + "date_format": {"type": str, "ls_field": "dateFormat"}, + "datetime_format": {"type": str, "ls_field": "dateTimeFormat"}, + "denominations": {"type": 'CurrencyDenomination', "multifield": True, "ls_field": "CurrencyDenominations.CurrencyDenomination"}, + "tax_name_1": {"type": str, "ls_field": "taxName1"}, + "tax_name_2": {"type": str, "ls_field": "taxName2"}, } \ No newline at end of file diff --git a/lightspeed_api/models/manufacturer.py b/lightspeed_api/models/manufacturer.py index d0d0d9d..996087f 100644 --- a/lightspeed_api/models/manufacturer.py +++ b/lightspeed_api/models/manufacturer.py @@ -1,7 +1,11 @@ +from datetime import datetime from . import BaseObject class Manufacturer(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "manufacturerID"}, + "name": {"type": str, "ls_field": "name"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "created_time": {"type": datetime, "ls_field": "createTime"}, } \ No newline at end of file diff --git a/lightspeed_api/models/options.py b/lightspeed_api/models/options.py index 6557a17..9c5895c 100644 --- a/lightspeed_api/models/options.py +++ b/lightspeed_api/models/options.py @@ -1,7 +1,15 @@ from . import BaseObject + +class Option(BaseObject): + _object_attributes = { + "name": {"type": str, "ls_field": "name"}, + "value": {"type": str, "ls_field": "value"}, + } + + class AccountOption(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": 'Option', "multifield": True, "ls_field": "Option"}, } \ No newline at end of file diff --git a/lightspeed_api/models/order.py b/lightspeed_api/models/order.py index 3a3f469..8aac0ec 100644 --- a/lightspeed_api/models/order.py +++ b/lightspeed_api/models/order.py @@ -1,25 +1,75 @@ +from datetime import datetime from . import BaseObject + class Order(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "orderID"}, + "ordered_time": {"type": datetime, "ls_field": "orderedDate"}, + "received_time": {"type": datetime, "ls_field": "receivedDate"}, + "arrival_time": {"type": datetime, "ls_field": "arrivalDate"}, + "reference_number": {"type": str, "ls_field": "refNum"}, + "shipping_instructions": {"type": str, "ls_field": "shipInstructions"}, + "stock_instructions": {"type": str, "ls_field": "stockInstructions"}, + "shipping_costs": {"type": float, "ls_field": "shipCost"}, + "other_costs": {"type": float, "ls_field": "otherCost"}, + "is_complete": {"type": bool, "ls_field": "complete"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "discount_percent": {"type": float, "ls_field": "discount"}, + "total_discount": {"type": float, "ls_field": "totalDiscount"}, + "total_quantity": {"type": int, "ls_field": "totalQuantity"}, + "vendor": {"type": 'Vendor', "ls_field": "Vendor", "ls_field_id": "vendorID", "relationships": ["Vendor"]}, + "note": {"type": 'Note', "ls_field": "Note", "ls_field_id": "noteID", "relationships": ["Note"]}, + "shop": {"type": 'Shop', "ls_field": "Shop", "ls_field_id": "shopID", "relationships": ["Shop"]}, + "custom_fields": {"type": 'OrderCustomFieldValue', "multifield": True, "ls_field": "CustomFieldValues.CustomFieldValue", "relationships": ["CustomFieldValues", "CustomFieldValues.value"]}, + "lines": {"type": 'OrderLine', "multifield": True, "ls_field": "OrderLines.OrderLine", "relationships": ["OrderLines"]}, } class OrderLine(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "orderLineID"}, + "quantity": {"type": int, "ls_field": "quantity"}, + "price": {"type": float, "ls_field": "price"}, + "original_price": {"type": float, "ls_field": "originalPrice"}, + "count_checked_in": {"type": int, "ls_field": "checkedIn"}, + "count_received": {"type": int, "ls_field": "numReceived"}, + "total": {"type": float, "ls_field": "total"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "order": {"type": 'Order', "ls_field_id": "orderID"}, + "item": {"type": 'Item', "ls_field_id": "itemID"}, } class OrderCustomField(BaseObject): _object_attributes = { "id": {"type": int, "ls_field": "customFieldID"}, + "name": {"type": str, "ls_field": "name"}, + "type": {"type": str, "ls_field": "type"}, + "units": {"type": str, "ls_field": "uom"}, + "decimal_precision": {"type": int, "ls_field": "decimalPrecision"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "default": {"type": str, "ls_field": "default"}, # TODO: may be a mixed type, so this may not work? + } + + +class OrderCustomFieldValue(BaseObject): + _object_attributes = { + "id": {"type": int, "ls_field": "customFieldValueID"}, + "custom_field": {"type": 'OrderCustomField', "ls_field_id": "customFieldID"}, + "is_deleted": {"type": bool, "ls_field": "deleted"}, + "name": {"type": str, "ls_field": "name"}, + "type": {"type": str, "ls_field": "type"}, # TODO: do we want to convert this to a Python type? + "value": {"type": str, "ls_field": "value"}, # TODO: do we want to convert this to its Python type? } class OrderCustomFieldChoice(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "customFieldChoiceID"}, + "name": {"type": str, "ls_field": "name"}, + "value": {"type": str, "ls_field": "value"}, + "is_deletable": {"type": bool, "ls_field": "canBeDeleted"}, + "custom_field": {"type": 'OrderCustomField', "ls_field_id": "customFieldID"}, } \ No newline at end of file diff --git a/lightspeed_api/models/payment_type.py b/lightspeed_api/models/payment_type.py index be6e756..df99ae3 100644 --- a/lightspeed_api/models/payment_type.py +++ b/lightspeed_api/models/payment_type.py @@ -3,5 +3,11 @@ class PaymentType(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "paymentTypeID"}, + "name": {"type": str, "ls_field": "name"}, + "is_require_customer": {"type": bool, "ls_field": "requireCustomer"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "is_internal_type": {"type": bool, "ls_field": "internalReserved"}, + "id": {"type": str, "ls_field": "type"}, # TODO: enum + "refunded_payment_type": {"type": 'PaymentType', "ls_field": "refundAsPaymentTypeID"}, } \ No newline at end of file diff --git a/lightspeed_api/models/price_level.py b/lightspeed_api/models/price_level.py index 77e8c60..a56a527 100644 --- a/lightspeed_api/models/price_level.py +++ b/lightspeed_api/models/price_level.py @@ -3,5 +3,10 @@ class PriceLevel(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "priceLevelID"}, + "name": {"type": str, "ls_field": "name"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "can_be_archived": {"type": bool, "ls_field": "canBeArchived"}, + "type": {"type": int, "ls_field": "type"}, # TODO: enum + "calculation": {"type": str, "ls_field": "Calculation"}, # TODO: "reference" type? } \ No newline at end of file diff --git a/lightspeed_api/models/register.py b/lightspeed_api/models/register.py index 76c0caa..9bd9aef 100644 --- a/lightspeed_api/models/register.py +++ b/lightspeed_api/models/register.py @@ -1,17 +1,31 @@ +from datetime import datetime from . import BaseObject class Register(BaseObject): - def __init__(self, obj=None, api=None): - if obj: - self.id = obj['registerID'] - self.name = obj['name'] + _object_attributes = { + "id": {"type": int, "ls_field": "registerID"}, + "name": {"type": str, "ls_field": "name"}, + "is_open": {"type": bool, "ls_field": "open"}, + "opened_time": {"type": datetime, "ls_field": "openTime"}, + "opened_by_employee": {"type": 'Employee', "ls_field": "openEmployeeID"}, + "shop": {"type": 'Shop', "ls_field": "shopID"}, + } + _get_function = "RegistersAPI.get_register" + _update_url = 'Register/%s.json' + _create_url = 'Register.json' @property def active_sale(self): return self.api.sales.get_active_sale_at_register(self.id) + def close(self): + pass + + def open(self): + pass + def __repr__(self): return self.name @@ -21,17 +35,47 @@ def __hash__(self): class RegisterCount(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "registerCountID"}, + "created_time": {"type": datetime, "ls_field": "createTime"}, + "opened_time": {"type": datetime, "ls_field": "openTime"}, + "notes": {"type": str, "ls_field": "notes"}, + "register": {"type": 'Register', "ls_field": "registerID"}, + "opened_by_employee": {"type": 'Employee', "ls_field": "openEmployeeID"}, + "closed_by_employee": {"type": 'Employee', "ls_field": "closeEmployeeID"}, + "amounts": {"type": 'RegisterCountAmount', "multifield": True, "ls_field": "RegisterCountAmounts.RegisterCountAmount", "relationships": ["RegisterCountAmounts", "RegisterCountAmounts.PaymentType"]}, } class RegisterCountAmount(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "registerCountAmountID"}, + "calculated_amount": {"type": float, "ls_field": "calculated"}, + "actual_amount": {"type": float, "ls_field": "actual"}, + "register": {"type": 'Register', "ls_field": "registerCountID"}, + "payment_type": {"type": 'PaymentType', "ls_field": "PaymentType", "ls_field_id": "paymentTypeID", "relationships": ["PaymentType"]}, } class RegisterWithdraw(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "registerWithdrawID"}, + "amount": {"type": float, "ls_field": "amount"}, + "created_time": {"type": datetime, "ls_field": "createTime"}, + "notes": {"type": str, "ls_field": "notes"}, + "register": {"type": 'Register', "ls_field": "registerID"}, + "employee": {"type": 'Employee', "ls_field": "employeeID"}, + "payment_type": {"type": 'PaymentType', "ls_field": "PaymentType", "ls_field_id": "paymentTypeID", "relationships": ["PaymentType"]}, + } + + +class ReceiptSetup(BaseObject): + _object_attributes = { + "id": {"type": int, "ls_field": "receiptSetupID"}, + "general_message": {"type": str, "ls_field": "generalMsg"}, + "workorder_agreement": {"type": str, "ls_field": "workorderAgree"}, + "credit_card_agreement": {"type": str, "ls_field": "creditcardAgree"}, + "logo_url": {"type": str, "ls_field": "logo"}, + "logo_height": {"type": int, "ls_field": "logoHeight"}, + "logo_width": {"type": int, "ls_field": "logoWidth"}, + "header": {"type": str, "ls_field": "header"}, } \ No newline at end of file diff --git a/lightspeed_api/models/sale.py b/lightspeed_api/models/sale.py index 1b27da7..fa27089 100644 --- a/lightspeed_api/models/sale.py +++ b/lightspeed_api/models/sale.py @@ -4,55 +4,101 @@ class Sale(BaseObject): - def __init__(self, obj=None, api=None): - if obj: - self.id = obj['saleID'] - self.timestamp = obj['timeStamp'] - self.completed = obj['completed'] - self.total = obj['calcTotal'] - self.customer_id = obj['customerID'] - self.internal_note_id = "0" if "SaleNotes" not in obj or "InternalNote" not in obj["SaleNotes"] else obj["SaleNotes"]["InternalNote"]['noteID'] - self.internal_note = "" if self.internal_note_id == "0" else obj["SaleNotes"]["InternalNote"]["note"] - self.printed_note_id = "0" if "SaleNotes" not in obj or "PrintedNote" not in obj["SaleNotes"] else obj["SaleNotes"]["PrintedNote"]['noteID'] - self.printed_note = "" if self.printed_note_id == "0" else obj["SaleNotes"]["PrintedNote"]['note'] - self._obj = obj - - @property - def items(self): - return self.api.sales.get_all_from_sale(self.id) - - def has_customer(self): - return self.customer_id != "0" + _object_attributes = { + "id": {"type": int, "ls_field": "saleID"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "is_completed": {"type": bool, "ls_field": "completed"}, + "is_voided": {"type": bool, "ls_field": "voided"}, + "is_promotions_enabled": {"type": bool, "ls_field": "enablePromotions"}, + "reference_number": {"type": str, "ls_field": "referenceNumber"}, + "reference_number_source": {"type": str, "ls_field": "referenceNumberSource"}, + "tax_rate_1": {"type": float, "ls_field": "tax1Rate"}, + "tax_rate_2": {"type": float, "ls_field": "tax2Rate"}, + "change": {"type": float, "ls_field": "change"}, + "discounted": {"type": float, "ls_field": "calcDiscount"}, + "total_before_discount": {"type": float, "ls_field": "calcTotal"}, + "subtotal": {"type": float, "ls_field": "calcSubtotal"}, + "subtotal_tax": {"type": float, "ls_field": "calcTaxable"}, + "subtotal_nontax": {"type": float, "ls_field": "calcNonTaxable"}, + "average_line": {"type": float, "ls_field": "calcAvgCost"}, + "fifo_cost": {"type": float, "ls_field": "calcFIFOCost"}, + "total_tax_1": {"type": float, "ls_field": "calcTax1"}, + "total_tax_2": {"type": float, "ls_field": "calcTax2"}, + "total_payments": {"type": float, "ls_field": "calcPayments"}, + "total": {"type": float, "ls_field": "total"}, + "due": {"type": float, "ls_field": "totalDue"}, + "displayable_total": {"type": float, "ls_field": "displayableTotal"}, + "balance": {"type": float, "ls_field": "balance"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "created_time": {"type": datetime, "ls_field": "createTime"}, + "updated_time": {"type": datetime, "ls_field": "updateTime"}, + "completed_time": {"type": datetime, "ls_field": "completeTime"}, + "customer": {"type": 'Customer', "ls_field": "Customer", "ls_field_id": "customerID", "relationships": ["Customer"]}, + "employee": {"type": 'Employee', "ls_field_id": "employeeID"}, + "discount": {"type": 'Discount', "ls_field": "Discount", "ls_field_id": "discountID", "relationships": ["Discount"]}, + "tax_category": {"type": 'TaxCategory', "ls_field": "TaxCategory", "ls_field_id": "taxCategoryID", "relationships": ["TaxCategory"]}, + "shop": {"type": 'Shop', "ls_field_id": "shopID"}, + "register": {"type": 'Shop', "ls_field_id": "registerID"}, + "items": {"type": 'SaleLine', "multifield": True, "ls_field": "SaleLines.SaleLine", "relationships": ["SaleLines"]}, + "note": {"type": 'Note', "ls_field": "Note", "relationships": ["SaleNotes"], 'optional': True}, + "payments": {"type": 'SalePayment', "multifield": True, "ls_field": "SalePayments.SalePayment", "relationships": ["SalePayments"]}, + "shipping_info": {"type": 'ShipTo', "ls_field": "ShipTo", "relationships": ["ShipTo"]}, + "quote": {"type": 'Quote', "ls_field": "Quote", "relationships": ["Quote"]}, + } + _get_function = "SalesAPI.get_sale" + _update_url = 'Sale/%s.json' + _create_url = 'Sale.json' - @property - def customer(self): - if self.customer_id == "0": - return None - return self.api.customers.get_customer(self.customer_id) - - def set_note(self, note): - return self.api.sales.set_note(self.id, note) - - -class SaleLineItem(BaseObject): - def __init__(self, obj=None, api=None): - if obj: - self.id = obj['saleLineID'] - self.sale_id = obj['saleID'] - self.item_id = obj['itemID'] - self.created_at = obj['createTime'] - self.total = obj['calcTotal'] - self.count = int(obj['unitQuantity']) - self._obj = obj + def email_receipt(self): + pass + + def refund(self): + pass + + +class SaleLine(BaseObject): + _object_attributes = { + "id": {"type": int, "ls_field": "saleLineID"}, + "is_layaway": {"type": bool, "ls_field": "isLayaway"}, + "is_workorder": {"type": bool, "ls_field": "isWorkorder"}, + "is_special_order": {"type": bool, "ls_field": "isSpecialOrder"}, + "is_taxed": {"type": bool, "ls_field": "tax"}, + "quantity": {"type": int, "ls_field": "unitQuantity"}, + "unit_price": {"type": float, "ls_field": "unitPrice"}, + "normal_unit_price": {"type": float, "ls_field": "normalUnitPrice"}, + "discounted": {"type": float, "ls_field": "discountAmount"}, + "discount_percent": {"type": float, "ls_field": "discountPercent"}, + "average_cost": {"type": float, "ls_field": "avgCost"}, + "fifo_cost": {"type": float, "ls_field": "fifoCost"}, + "tax_rate_1": {"type": float, "ls_field": "tax1Rate"}, + "tax_rate_2": {"type": float, "ls_field": "tax2Rate"}, + "displayed_subtotal": {"type": float, "ls_field": "displayableSubtotal"}, + "displayed_unit_price": {"type": float, "ls_field": "displayableUnitPrice"}, + "line_discounted": {"type": float, "ls_field": "calcLineDiscount"}, + "transaction_discounted": {"type": float, "ls_field": "calcTransactionDiscount"}, + "total": {"type": float, "ls_field": "calcTotal"}, + "subtotal": {"type": float, "ls_field": "calcSubtotal"}, + "total_tax_1": {"type": float, "ls_field": "calcTax1"}, + "total_tax_2": {"type": float, "ls_field": "calcTax2"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "created_time": {"type": datetime, "ls_field": "createTime"}, + "discount": {"type": 'Discount', "ls_field": "Discount", "ls_field_id": "discountID", "relationships": ["Discount"]}, + "tax_category": {"type": 'TaxCategory', "ls_field": "TaxCategory", "ls_field_id": "taxCategoryID", "relationships": ["TaxCategory"]}, + "tax_class": {"type": 'TaxClass', "ls_field": "TaxClass", "ls_field_id": "taxClassID", "relationships": ["TaxClass"]}, + "customer": {"type": 'Customer', "ls_field_id": "customerID "}, + "shop": {"type": 'Shop', "ls_field_id": "shopID"}, + "employee": {"type": 'Employee', "ls_field_id": "employeeID"}, + "register": {"type": 'Shop', "ls_field_id": "registerID"}, + "sale": {"type": 'Sale', "ls_field_id": "saleID"}, + "parent_sale_line": {"type": 'SaleLine', "ls_field_id": "parentSaleLineID"}, + "item": {"type": 'Item', "ls_field": "Item", "ls_field_id": "itemID", "relationships": ["Item"]}, + "note": {"type": 'Note', "ls_field": "Note", "ls_field_id": "noteID", "relationships": ["Note"], 'optional': True}, + } @property def tags(self): return self.api.items.get_tags_for_item(self.item_id) - @property - def item(self): - return self.api.items.get_item(self.item_id) - @property # TODO: figure out how to pass in the object (if it existed) instead of re-looking it up. def sale(self): @@ -64,23 +110,71 @@ def set_note(self, note): class Quote(BaseObject): search_terms = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "quoteID"}, + "issue_time": {"type": datetime, "ls_field": "issueDate"}, + "notes": {"type": str, "ls_field": "notes"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "employee": {"type": 'Employee', "ls_field": "employeeID"}, + "sale": {"type": 'Sale', "ls_field": "saleID"}, } class SalePayment(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "salePaymentID"}, + "amount": {"type": float, "ls_field": "amount"}, + "created_time": {"type": datetime, "ls_field": "createTime"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "client_reference": {"type": str, "ls_field": "remoteReference"}, + "payment_reference": {"type": str, "ls_field": "paymentID"}, + "type": {"type": 'PaymentType', "ls_field": "PaymentType", "ls_field_id": "paymentTypeID", "relationships": ["PaymentType"]}, + "credit_card_charge": {"type": 'CreditCardCharge', "ls_field": "CCCharge", "ls_field_id": "ccChargeID", "relationships": ["CCCharge"]}, + "sale": {"type": 'Sale', "ls_field": "saleID"}, + "referenced_sale_payment": {"type": 'SalePayment', "ls_field": "refPaymentID"}, + "register": {"type": 'Register', "ls_field": "registerID"}, + "credit_account": {"type": 'CreditAccount', "ls_field": "creditAccountID"}, + "employee": {"type": 'Employee', "ls_field": "employeeID"}, + "sale_account": {"type": 'SaleAccount', "ls_field": "SaleAccounts", "relationships": ["SaleAccounts"]}, + } + + +class SaleAccount(BaseObject): + _object_attributes = { + "credit_account": {"type": 'CreditAccount', "ls_field": "creditAccountID"}, + "payment": {"type": 'SalePayment', "ls_field": "salePaymentID"}, + "sale_line": {"type": 'SaleLine', "ls_field": "saleLineID"}, + } + + +class SalePaymentSignature(BaseObject): + _object_attributes = { + "id": {"type": int, "ls_field": "salePaymentSignatureID"}, + "created_time": {"type": datetime, "ls_field": "createTime"}, + "payment": {"type": int, "ls_field": "salePaymentID"}, } class SaleVoid(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "saleVoidID"}, + "created_time": {"type": datetime, "ls_field": "createTime"}, + "reason": {"type": str, "ls_field": "reason"}, + "sale": {"type": 'Sale', "ls_field": "saleID"}, + "register": {"type": 'Register', "ls_field": "registerID"}, + "employee": {"type": 'Employee', "ls_field": "employeeID"}, } class SpecialOrder(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "specialOrderID"}, + "is_customer_contacted": {"type": bool, "ls_field": "contacted"}, + "is_completed": {"type": bool, "ls_field": "completed"}, + "quantity": {"type": int, "ls_field": "unitQuantity"}, + "customer": {"type": 'Customer', "ls_field": "customerID"}, + "shop": {"type": 'Shop', "ls_field": "shopID"}, + "transfer_item": {"type": 'InventoryTransferItem', "ls_field": "transferItemID"}, + "sale_line": {"type": 'SaleLine', "ls_field": "SaleLine", "relationships": ["SaleLine"]}, + "order_line": {"type": 'OrderLine', "ls_field": "OrderLine", "relationships": ["OrderLine"]}, + "note": {"type": 'Note', "ls_field": "Note", "relationships": ["Note"], 'optional': True}, } \ No newline at end of file diff --git a/lightspeed_api/models/serialized.py b/lightspeed_api/models/serialized.py index 8ea9898..b8b8677 100644 --- a/lightspeed_api/models/serialized.py +++ b/lightspeed_api/models/serialized.py @@ -1,7 +1,17 @@ +from datetime import datetime from . import BaseObject + class SerializedItem(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "serializedID"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "color": {"type": str, "ls_field": "colorName"}, + "size": {"type": str, "ls_field": "sizeName"}, + "serial_numer": {"type": str, "ls_field": "serial"}, + "description": {"type": str, "ls_field": "description"}, + "item": {"type": 'Item', "ls_field": "itemID"}, + "sale_line": {"type": 'SaleLine', "ls_field": "saleLineID"}, + "customer": {"type": 'Customer', "ls_field": "customerID"}, } \ No newline at end of file diff --git a/lightspeed_api/models/session.py b/lightspeed_api/models/session.py index ff21ee5..6f97c00 100644 --- a/lightspeed_api/models/session.py +++ b/lightspeed_api/models/session.py @@ -3,5 +3,12 @@ class Session(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "sessionID"}, + "session_cookie": {"type": str, "ls_field": "sessionCookie"}, + "employee": {"type": 'Employee', "ls_field_id": "employeeID"}, + "account_id": {"type": int, "ls_field_id": "systemCustomerID"}, + "employee_user_id": {"type": int, "ls_field_id": "systemUserID"}, + "api_client_id": {"type": int, "ls_field_id": "systemAPIClientID"}, + "api_key_id": {"type": int, "ls_field_id": "systemAPIKeyID"}, + "shop_count": {"type": int, "ls_field_id": "shopCount"}, } \ No newline at end of file diff --git a/lightspeed_api/models/shipping.py b/lightspeed_api/models/shipping.py index 590ea75..992f9ef 100644 --- a/lightspeed_api/models/shipping.py +++ b/lightspeed_api/models/shipping.py @@ -1,7 +1,17 @@ +from datetime import datetime from . import BaseObject + class ShipTo(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "shipToID"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "is_shipped": {"type": bool, "ls_field": "shipped"}, + "notes": {"type": str, "ls_field": "shipNote"}, + "first_name": {"type": str, "ls_field": "firstName"}, + "last_name": {"type": str, "ls_field": "lastName"}, + "customer": {"type": 'Customer', "ls_field_id": "customerID"}, + "sale": {"type": 'Sale', "ls_field_id": "saleID"}, + "shipping_address": {"type": 'Contact', "ls_field": "Contact", "relationships": ["Contact"]}, # TODO: contact mapping be different } \ No newline at end of file diff --git a/lightspeed_api/models/shop.py b/lightspeed_api/models/shop.py index 2ab3989..18a11e4 100644 --- a/lightspeed_api/models/shop.py +++ b/lightspeed_api/models/shop.py @@ -3,5 +3,18 @@ class Shop(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "shopID"}, + "name": {"type": str, "ls_field": "name"}, + "labor_rate": {"type": float, "ls_field": "serviceRate"}, + "timezone": {"type": str, "ls_field": "timeZone"}, # TODO: enum? + "is_labor_taxed": {"type": bool, "ls_field": "taxLabor"}, + "product_title_label": {"type": str, "ls_field": "labelTitle"}, # TODO: ENUM + "is_msrp_on_label": {"type": bool, "ls_field": "labelMsrp"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "contact": {"type": 'Contact', "ls_field": "Contact", "ls_field_id": "contactID", "relationships": ["Contact"]}, # TODO: implement contact + "tax_category": {"type": 'TaxCategory', "ls_field": "TaxCategory", "ls_field_id": "taxCategoryID", "relationships": ["TaxCategory"]}, + "receipt_setup": {"type": 'ReceiptSetup', "ls_field": "ReceiptSetup", "ls_field_id": "receiptSetupID", "relationships": ["ReceiptSetup"]}, + "credit_card_gateway": {"type": 'CreditCardGateway', "ls_field": "CCGateway", "ls_field_id": "ccGatewayID", "relationships": ["PriceLevel"]}, + "price_level": {"type": 'PriceLevel', "ls_field": "PriceLevel", "ls_field_id": "priceLevelID", "relationships": ["CCGateway"]}, + "registers": {"type": 'Register', "multifield": True, "ls_field": "Registers.Register", "relationships": ["Registers"]}, } \ No newline at end of file diff --git a/lightspeed_api/models/tag.py b/lightspeed_api/models/tag.py index 5873aab..6138cce 100644 --- a/lightspeed_api/models/tag.py +++ b/lightspeed_api/models/tag.py @@ -31,6 +31,7 @@ def __eq__(self, item): # TODO: explore this a bit more before we go do it +# Unsure of what it will do/have (it's an XML format?) class TagGroup(BaseObject): _object_attributes = { "id": {"type": int, "ls_field": "customFieldID"}, diff --git a/lightspeed_api/models/tax.py b/lightspeed_api/models/tax.py index c4c9aee..652a688 100644 --- a/lightspeed_api/models/tax.py +++ b/lightspeed_api/models/tax.py @@ -1,19 +1,30 @@ +from datetime import datetime from . import BaseObject class TaxCategory(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "taxCategoryID"}, + "is_tax_inclusive": {"type": bool, "ls_field": "isTaxInclusive"}, + "tax_name_1": {"type": str, "ls_field": "tax1Name"}, + "tax_name_2": {"type": str, "ls_field": "tax2Name"}, + "tax_rate_1": {"type": float, "ls_field": "tax1Rate"}, + "tax_rate_2": {"type": float, "ls_field": "tax2Rate"}, + "tax_classes": {"type": 'TaxCategoryClass', "multifield": True, "ls_field": "TaxCategoryClasses.TaxCategoryClass", "relationships": ["TaxCategoryClasses", "TaxCategoryClasses.TaxClass"]}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, } class TaxCategoryClass(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "taxCategoryClassID"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, } class TaxClass(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "taxClassID"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "name": {"type": str, "ls_field": "name"}, } \ No newline at end of file diff --git a/lightspeed_api/models/vendor.py b/lightspeed_api/models/vendor.py new file mode 100644 index 0000000..dc32bc2 --- /dev/null +++ b/lightspeed_api/models/vendor.py @@ -0,0 +1,38 @@ + +from datetime import datetime +from . import BaseObject + +class VendorRepresentative(BaseObject): + _object_attributes = { + "first_name": {"type": str, "ls_field": "firstName"}, + "last_name": {"type": str, "ls_field": "lastName"}, + "name": {"combine": ["first_name", "last_name"]}, + } + +class Vendor(BaseObject): + _object_attributes = { + "id": {"type": int, "ls_field": "vendorID"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "name": {"type": str, "ls_field": "name"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "account_number": {"type": str, "ls_field": "accountNumber"}, + "price_level": {"type": str, "ls_field": "priceLevel"}, + "is_update_price": {"type": bool, "ls_field": "updatePrice"}, + "is_update_cost": {"type": bool, "ls_field": "updateCost"}, + "is_update_description": {"type": bool, "ls_field": "updateDescription"}, + "is_sell_through_data_enabled": {"type": bool, "ls_field": "shareSellThrough"}, + "contact": {"type": 'Contact', "optional": True, "ls_field": "Contact", "relationships": ["Contact"]}, + "representatives": {"type": 'VendorRepresentative', "multifield": True, "ls_field": "Reps.VendorRep"}, + } + _get_function = "VendorsAPI.get_vendor" + _update_url = 'Vendor/%s.json' + _create_url = 'Vendor.json' + + def delete(self): + self.api.request('DELETE', f'Vendor/{self.id}.json') + + def __repr__(self): + return self.name + + def __hash__(self): + return hash(self.name) \ No newline at end of file diff --git a/lightspeed_api/models/workorder.py b/lightspeed_api/models/workorder.py index 5b75b24..bbaa0ef 100644 --- a/lightspeed_api/models/workorder.py +++ b/lightspeed_api/models/workorder.py @@ -1,25 +1,87 @@ +from datetime import datetime from . import BaseObject class Workorder(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "workorderID"}, + "received_time": {"type": datetime, "ls_field": "timeIn"}, + "estimated_out_time": {"type": datetime, "ls_field": "etaOut"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "note": {"type": str, "ls_field": "note"}, + "is_warrantied": {"type": bool, "ls_field": "warranty"}, + "is_taxed": {"type": bool, "ls_field": "tax"}, + "is_archived": {"type": bool, "ls_field": "archived"}, + "hook_in": {"type": str, "ls_field": "hookIn"}, + "hook_out": {"type": str, "ls_field": "hookOut"}, + "is_saving_parts": {"type": bool, "ls_field": "saveParts"}, + "is_same_employee_for_all_lines": {"type": bool, "ls_field": "assignEmployeeToAll"}, + "customer": {"type": 'Customer', "ls_field": "Customer", "ls_field_id": "customerID", "relationships": ["Customer"]}, + "discount": {"type": 'Discount', "ls_field": "Discount", "ls_field_id": "discountID", "relationships": ["Discount"]}, + "employee": {"type": 'Employee', "ls_field": "Employee", "ls_field_id": "employeeID", "relationships": ["Employee"]}, + "serialized_object": {"type": 'Serialized', "ls_field": "Serialized", "ls_field_id": "serializedID", "relationships": ["Serialized"]}, + "shop": {"type": 'Shop', "ls_field_id": "shopID"}, + "sale": {"type": 'Sale', "ls_field_id": "saleID"}, + "sale_line": {"type": 'SaleLine', "ls_field_id": "saleLineID"}, + "status": {"type": 'WorkorderStatus', "ls_field": "WorkorderStatus", "ls_field_id": "workorderStatusID", "relationships": ["WorkorderStatus"]}, + "items": {"type": 'WorkorderItem', "multifield": True, "ls_field": "WorkorderItems.WorkorderItem", "relationships": ["WorkorderItems"]}, + "lines": {"type": 'WorkorderLine', "multifield": True, "ls_field": "WorkorderLines.WorkorderLine", "relationships": ["WorkorderLines"]}, } + _get_function = "WorkordersAPI.get_workorder" class WorkorderItem(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "workorderItemID"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "quantity": {"type": int, "ls_field": "unitQuantity"}, + "price": {"type": float, "ls_field": "unitPrice"}, + "is_approved": {"type": bool, "ls_field": "approved"}, + "is_warrantied": {"type": bool, "ls_field": "warranty"}, + "is_taxed": {"type": bool, "ls_field": "tax"}, + "is_special_order": {"type": bool, "ls_field": "isSpecialOrder"}, + "note": {"type": str, "ls_field": "note"}, + "workorder": {"type": 'Workorder', "ls_field_id": "workorderID"}, + "employee": {"type": 'Employee', "ls_field_id": "employeeID"}, + "sale_line": {"type": 'SaleLine', "ls_field_id": "saleLineID"}, + "sale": {"type": 'Sale', "ls_field_id": "saleID"}, + "item": {"type": 'Item', "ls_field_id": "itemID"}, + "discount": {"type": 'Discount', "ls_field": "Discount", "ls_field_id": "discountID", "relationships": ["Discount"]}, } + _get_function = "WorkordersAPI.get_workorder_item" class WorkorderLine(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, + "id": {"type": int, "ls_field": "workorderLineID"}, + "note": {"type": str, "ls_field": "note"}, + "last_modified_time": {"type": datetime, "ls_field": "timeStamp"}, + "hours": {"type": int, "ls_field": "hours"}, + "minutes": {"type": int, "ls_field": "minutes"}, + "price_override": {"type": float, "ls_field": "unitPriceOverride"}, + "quantity": {"type": int, "ls_field": "unitQuantity"}, + "cost": {"type": float, "ls_field": "unitCost"}, + "is_done": {"type": bool, "ls_field": "done"}, + "is_approved": {"type": bool, "ls_field": "approved"}, + "is_warrantied": {"type": bool, "ls_field": "warranty"}, + "is_taxed": {"type": bool, "ls_field": "tax"}, + "workorder": {"type": 'Workorder', "ls_field_id": "workorderID"}, + "employee": {"type": 'Employee', "ls_field_id": "employeeID"}, + "sale_line": {"type": 'SaleLine', "ls_field_id": "saleLineID"}, + "sale": {"type": 'Sale', "ls_field_id": "saleID"}, + "item": {"type": 'Item', "ls_field_id": "itemID"}, + "discount": {"type": 'Discount', "ls_field": "Discount", "ls_field_id": "discountID", "relationships": ["Discount"]}, + "tax_class": {"type": 'TaxClass', "ls_field": "TaxClass", "ls_field_id": "taxClassID", "relationships": ["TaxClass"]}, } + _get_function = "WorkordersAPI.get_workorder_line" class WorkorderStatus(BaseObject): _object_attributes = { - "id": {"type": int, "ls_field": "customFieldID"}, - } \ No newline at end of file + "id": {"type": int, "ls_field": "workorderStatusID"}, + "name": {"type": str, "ls_field": "name"}, + "sort_order": {"type": int, "ls_field": "sortOrder"}, + "color": {"type": str, "ls_field": "htmlColor"}, + "system_value": {"type": str, "ls_field": "systemValue"}, + } + _get_function = "WorkordersAPI.get_workorder_status" diff --git a/lightspeed_api/utils.py b/lightspeed_api/utils.py index 25c9e06..c0362f6 100644 --- a/lightspeed_api/utils.py +++ b/lightspeed_api/utils.py @@ -19,6 +19,11 @@ def convert_to_type(obj_type, obj): return int(obj) except ValueError: raise Exception("The value %s could not be converted to a number" % obj) + elif obj_type == float: + try: + return float(obj) + except ValueError: + raise Exception("The value %s could not be converted to a floating-point integer" % obj) elif obj_type == datetime: try: dtobj = datetime.strptime(obj, LIGHTSPEED_DATETIME_FORMAT) @@ -41,6 +46,10 @@ def convert_from_type(obj_type, obj): return str(obj) # Done to force conversion of Object to str if applicable elif obj_type == int: return str(obj) + elif obj_type == float: + return str(obj) + elif obj_type == bool: + return "true" if obj else "false" elif obj_type in [datetime, date]: try: dtobj = datetime.strftime(obj, LIGHTSPEED_DATETIME_FORMAT) diff --git a/tests/__pycache__/__init__.cpython-39.pyc b/tests/__pycache__/__init__.cpython-39.pyc index f4ad510..0e6ab34 100644 Binary files a/tests/__pycache__/__init__.cpython-39.pyc and b/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/tests/__pycache__/conftest.cpython-39-pytest-6.2.3.pyc b/tests/__pycache__/conftest.cpython-39-pytest-6.2.3.pyc index 5ff722a..880c111 100644 Binary files a/tests/__pycache__/conftest.cpython-39-pytest-6.2.3.pyc and b/tests/__pycache__/conftest.cpython-39-pytest-6.2.3.pyc differ diff --git a/tests/__pycache__/test_customers.cpython-39-pytest-6.2.3.pyc b/tests/__pycache__/test_customers.cpython-39-pytest-6.2.3.pyc index 9c8c9e2..2e206d8 100644 Binary files a/tests/__pycache__/test_customers.cpython-39-pytest-6.2.3.pyc and b/tests/__pycache__/test_customers.cpython-39-pytest-6.2.3.pyc differ diff --git a/tests/__pycache__/test_search.cpython-39-pytest-6.2.3.pyc b/tests/__pycache__/test_search.cpython-39-pytest-6.2.3.pyc index e674414..8a5f673 100644 Binary files a/tests/__pycache__/test_search.cpython-39-pytest-6.2.3.pyc and b/tests/__pycache__/test_search.cpython-39-pytest-6.2.3.pyc differ diff --git a/tests/__pycache__/test_tags.cpython-39-pytest-6.2.3.pyc b/tests/__pycache__/test_tags.cpython-39-pytest-6.2.3.pyc index 943504b..8a6e9bb 100644 Binary files a/tests/__pycache__/test_tags.cpython-39-pytest-6.2.3.pyc and b/tests/__pycache__/test_tags.cpython-39-pytest-6.2.3.pyc differ diff --git a/tests/test_customers.py b/tests/test_customers.py index 5fddb13..bbb85c6 100644 --- a/tests/test_customers.py +++ b/tests/test_customers.py @@ -1,5 +1,6 @@ -from datetime import datetime +import json +from datetime import datetime, timedelta import lightspeed_api @@ -42,15 +43,16 @@ def test_customer_create_complex(ls_client): customer.company = "Home Depot" customer.type = ls_client.api.customers.get_customer_type(1) + customer.contact = lightspeed_api.models.Contact() email1 = lightspeed_api.models.CustomerEmail("tst@hdepot.com", lightspeed_api.models.CustomerEmailType.PRIMARY) email2 = lightspeed_api.models.CustomerEmail("someotheremail@fakenews.com", lightspeed_api.models.CustomerEmailType.SECONDARY) - customer.emails = [email1, email2] + customer.contact.emails = [email1, email2] phone1 = lightspeed_api.models.CustomerPhoneNumber("+16138311828", lightspeed_api.models.CustomerPhoneNumberType.WORK) phone2 = lightspeed_api.models.CustomerPhoneNumber("6131234567", lightspeed_api.models.CustomerPhoneNumberType.FAX) - customer.phone_numbers = [phone1, phone2] + customer.contact.phone_numbers = [phone1, phone2] - customer.website = "www.google.com" + customer.contact.website = "www.google.com" address1 = lightspeed_api.models.CustomerAddress() address1.address_line_1 = "123 Fake St" @@ -59,7 +61,7 @@ def test_customer_create_complex(ls_client): address1.state = "Ontario" address1.country = "Canada" address1.zipcode = "A1A 1A1" - customer.addresses.append(address1) + customer.contact.addresses.append(address1) t1 = lightspeed_api.models.Tag() t1.name = "fullprofile" @@ -85,10 +87,10 @@ def test_customer_create_complex(ls_client): #assert(customer.birthday == customer_copy.birthday) assert(customer.company == customer_copy.company) assert(customer.type == customer_copy.type) - assert(customer.emails == customer_copy.emails) + assert(customer.contact.emails == customer_copy.contact.emails) assert(customer.tags == customer_copy.tags) assert(customer.note == customer_copy.note) - assert(customer_copy.note.last_modified_time < datetime.utcnow()) + assert(customer_copy.note.last_modified_time < (datetime.utcnow() + timedelta(seconds=10))) # time on server may be skewed a few seconds except AssertionError as ex: customer.delete() # Delete if things failed. raise ex @@ -139,15 +141,15 @@ def test_customer_get_created_from_ls(ls_client): assert(customer.type is not None) assert(customer.type.id == 1) - assert(len(customer.emails) == 2) - for t in customer.emails: + assert(len(customer.contact.emails) == 2) + for t in customer.contact.emails: assert(t is not None) assert(t.address is not None) assert(t.type == lightspeed_api.models.CustomerEmailType.PRIMARY or t.type == lightspeed_api.models.CustomerEmailType.SECONDARY) - assert(len(customer.phone_numbers) == 5) - for t in customer.phone_numbers: + assert(len(customer.contact.phone_numbers) == 5) + for t in customer.contact.phone_numbers: assert(t is not None) assert(t.number is not None) if t.number == "1111111111": @@ -163,8 +165,8 @@ def test_customer_get_created_from_ls(ls_client): else: assert(False) - assert(len(customer.addresses) == 1) - for t in customer.addresses: + assert(len(customer.contact.addresses) == 1) + for t in customer.contact.addresses: assert(t is not None) assert(t.address_line_1 == "1 Wellington St") assert(t.address_line_2 == "ATTN: Someone") @@ -177,7 +179,7 @@ def test_customer_get_created_from_ls(ls_client): for t in customer.tags: assert(t.id) - assert(customer.website == "www.canada.ca") + assert(customer.contact.website == "www.canada.ca") assert(customer.credit_account is not None) assert(customer.discount is not None) assert(customer.discount.id == 1) @@ -207,6 +209,13 @@ def test_customer_get_created_from_ls(ls_client): assert(customer.name == "Richard Desmarais") assert(customer.company == "Dayna's Pet Sitting") + # Make sure what we get in JSON format is the same as we expect to send back + raw_obj = ls_client.api.customers.get_customer(customer.id, raw=True) + raw_local_obj = json.loads(customer.json()) + #for f in raw_obj: + #assert(f in raw_local_obj) + #assert(raw_obj[f] == raw_local_obj[f]) + def test_customer_check_types_from_ls(ls_client): # TODO: write test diff --git a/tests/test_tags.py b/tests/test_tags.py index 8d172a5..2ced94d 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -1,6 +1,6 @@ def test_get_tags_premade(ls_client): tag_list = ls_client.api.tags.all() - assert(len(tag_list) == 4) + assert(len(tag_list) == 5) for t in tag_list: assert(t.id) diff --git a/tests/test_vendors.py b/tests/test_vendors.py new file mode 100644 index 0000000..1e29778 --- /dev/null +++ b/tests/test_vendors.py @@ -0,0 +1,53 @@ + +from datetime import datetime +import lightspeed_api + + +def test_vendor_create(ls_client): + vendor = lightspeed_api.models.Vendor() + vendor.name = "Fake Vendor" + ls_client.api.add(vendor) + vendor.save() + assert(vendor.id != None) + + id = vendor.id + vendor.account_number = "123456789" + vendor.save() + assert(vendor.id == id) + assert(vendor.name == "Fake Vendor") + assert(vendor.account_number == "123456789") + + vendor.delete() + vendor_list = ls_client.api.vendors.all(id=id) + assert(len(vendor_list) == 0) + + +def test_vendor_create_invalid(ls_client): + vendor = lightspeed_api.models.Vendor() + ls_client.api.add(vendor) + try: + vendor.save() + assert(False) + except Exception as ex: + pass # Success case + + +def test_vendor_all_functions(ls_client): + obj_list = ls_client.api.vendors.all() + assert(len(obj_list) == 9) + + +def test_vendor_get_functions(ls_client): + obj = ls_client.api.vendors.get_vendor(1) + assert(obj is not None) + + +def test_vendor_get_created_from_ls(ls_client): + vendor = ls_client.api.vendors.get_vendor(1) + assert(vendor is not None) + + assert(len(vendor.representatives) == 1) + for t in vendor.representatives: + assert(t is not None) + assert(isinstance(t, lightspeed_api.models.VendorRepresentative)) + assert(t.name=="John Doe") diff --git a/tests/test_workorders.py b/tests/test_workorders.py new file mode 100644 index 0000000..2b01b2f --- /dev/null +++ b/tests/test_workorders.py @@ -0,0 +1,182 @@ + +from datetime import datetime +import lightspeed_api + + +def test_workorder_create_delete(ls_client): + workorder = lightspeed_api.models.Workorder() + workorder.received_time = datetime.utcnow() + ls_client.api.add(workorder) + + try: + workorder.save() + assert(False) + except Exception: + pass + + workorder = ls_client.api.workorders.get_workorder(1) + try: + workorder.delete() + assert(False) + except Exception: + pass + + +def test_workorder_item_create_delete(ls_client): + workorder = lightspeed_api.models.WorkorderItem() + workorder.workorder = ls_client.api.workorders.get_workorder(1) + workorder.price = 1.0 + workorder.quantity = 1 + ls_client.api.add(workorder) + + try: + workorder.save() + assert(False) + except Exception: + pass + + workorder = ls_client.api.workorders.get_workorder_item(1) + try: + workorder.delete() + assert(False) + except Exception: + pass + + +def test_workorder_line_create_delete(ls_client): + workorder = lightspeed_api.models.WorkorderLine() + workorder.workorder = ls_client.api.workorders.get_workorder(1) + workorder.cost = 100.0 + workorder.quantity = 1 + workorder.hours = 1 + ls_client.api.add(workorder) + + try: + workorder.save() + assert(False) + except Exception: + pass + + workorder = ls_client.api.workorders.get_workorder_line(1) + try: + workorder.delete() + assert(False) + except Exception: + pass + + +def test_workorder_all_functions(ls_client): + obj_list = ls_client.api.workorders.all() + assert(len(obj_list) > 0) + + obj_list = ls_client.api.workorders.all_items() + assert(len(obj_list) > 0) + + obj_list = ls_client.api.workorders.all_lines() + assert(len(obj_list) > 0) + + obj_list = ls_client.api.workorders.all_statuses() + assert(len(obj_list) > 0) + + +def test_workorder_get_functions(ls_client): + obj = ls_client.api.workorders.get_workorder(1) + assert(obj is not None) + + obj = ls_client.api.workorders.get_workorder_item(1) + assert(obj is not None) + + obj = ls_client.api.workorders.get_workorder_line(1) + assert(obj is not None) + + obj = ls_client.api.workorders.get_workorder_status(1) + assert(obj is not None) + + +def test_workorder_get_created_from_ls(ls_client): + workorder = ls_client.api.workorders.get_workorder(1) + assert(workorder is not None) + assert(workorder.received_time is not None and workorder.received_time < datetime.utcnow()) + assert(workorder.estimated_out_time is not None and workorder.received_time < datetime.utcnow()) + assert(workorder.last_modified_time is not None and workorder.received_time < datetime.utcnow()) + assert(workorder.note == "Receipt note to be added here!") + assert(not workorder.is_warrantied) + assert(not workorder.is_taxed) + assert(not workorder.is_archived) + assert(workorder.hook_in == "2") + assert(workorder.hook_out == "") + assert(workorder.is_saving_parts) + assert(not workorder.is_same_employee_for_all_lines) + assert(workorder.customer is not None) + assert(workorder.customer.id == 285) + assert(workorder.customer.name == "Jim Bob") + assert(workorder.discount is None) + assert(workorder.employee is not None) + assert(workorder.serialized_object is None) + assert(workorder.shop is not None) + assert(workorder.sale is None) + assert(workorder.sale_line is not None) + assert(workorder.status is not None) + + assert(len(workorder.items) == 1) + for t in workorder.items: + assert(t is not None) + assert(t.last_modified_time is not None and t.last_modified_time < datetime.utcnow()) + assert(t.item is not None and t.item.id != 0) + + assert(len(workorder.lines) == 1) + for t in workorder.lines: + assert(t is not None) + assert(t.last_modified_time is not None and t.last_modified_time < datetime.utcnow()) + assert(t.sale_line is not None and t.sale_line.id != 0) + + +def test_workorder_item_created_from_ls(ls_client): + workorder = ls_client.api.workorders.get_workorder_item(1) + assert(workorder is not None) + assert(workorder.last_modified_time is not None and workorder.last_modified_time < datetime.utcnow()) + assert(workorder.note == "") + assert(workorder.is_warrantied) + assert(not workorder.is_approved) + assert(workorder.is_taxed) + assert(workorder.is_special_order) + assert(workorder.workorder is not None) + assert(workorder.workorder.id == 1) + assert(workorder.discount is None) + assert(workorder.item is not None) + assert(workorder.sale is None) + assert(workorder.employee is not None) + assert(workorder.sale_line is not None) + + +def test_workorder_line_created_from_ls(ls_client): + workorder = ls_client.api.workorders.get_workorder_line(1) + assert(workorder is not None) + assert(workorder.last_modified_time is not None and workorder.last_modified_time < datetime.utcnow()) + assert(workorder.note == "Labor") + assert(workorder.hours == 1) + assert(workorder.minutes == 0) + assert(workorder.price_override == 0.0) + assert(workorder.quantity == 1) + assert(workorder.cost == 125.0) + assert(not workorder.is_warrantied) + assert(not workorder.is_approved) + assert(not workorder.is_taxed) + assert(not workorder.is_done) + assert(workorder.workorder is not None) + assert(workorder.workorder.id == 1) + assert(workorder.discount is None) + assert(workorder.item is None) + assert(workorder.sale is None) + assert(workorder.employee is not None) + assert(workorder.sale_line is not None) + assert(workorder.tax_class is not None) + + +def test_workorder_status_created_from_ls(ls_client): + workorder = ls_client.api.workorders.get_workorder_status(1) + assert(workorder is not None) + assert(workorder.name == "Open") + assert(workorder.sort_order == 0) + assert(workorder.color == "") + assert(workorder.system_value == "open")