Changed renamed source directory to pyinvoiceninja
This commit is contained in:
parent
cc34db9ed1
commit
deacda0cfa
@ -1,74 +0,0 @@
|
|||||||
import requests
|
|
||||||
|
|
||||||
class InvoiceNinja(object):
|
|
||||||
API_V1 = 'api/v1'
|
|
||||||
ENDPOINT_URLS = {
|
|
||||||
'clients': 'clients',
|
|
||||||
'products': 'products',
|
|
||||||
'invoices': 'invoices',
|
|
||||||
'recurring invoices': 'recurring_invoices',
|
|
||||||
'payments': 'payments',
|
|
||||||
'quotes': 'quotes',
|
|
||||||
'credits': 'credits',
|
|
||||||
'reports': 'reports',
|
|
||||||
'activities': 'activities',
|
|
||||||
'charts': 'charts',
|
|
||||||
'companies': 'companies',
|
|
||||||
'documents': 'documents',
|
|
||||||
'emails': 'emails',
|
|
||||||
'expense': 'expenses',
|
|
||||||
'export': 'export',
|
|
||||||
'import': 'import_json',
|
|
||||||
'ping': 'ping',
|
|
||||||
'health check': 'health_check',
|
|
||||||
'users': 'users'
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
endpoint_url: str = 'https://invoicing.co',
|
|
||||||
api_token: str = str()):
|
|
||||||
self.endpoint_url = '{}/{}'.format(endpoint_url, self.API_V1)
|
|
||||||
self.api_token = api_token
|
|
||||||
self.headers = self.build_headers()
|
|
||||||
|
|
||||||
def _get_url_for(self, endpoint: str = 'ping'):
|
|
||||||
'''
|
|
||||||
Get complete URL for an endpoint.
|
|
||||||
|
|
||||||
Endpoint URLs are appended to the Invoice Ninja base URL
|
|
||||||
and returned.
|
|
||||||
'''
|
|
||||||
if endpoint in self.ENDPOINT_URLS:
|
|
||||||
return '{}/{}'.format(self.endpoint_url,
|
|
||||||
self.ENDPOINT_URLS[endpoint])
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise KeyError('Endpoint URL not found')
|
|
||||||
|
|
||||||
#def _get_headers(self, headers: dict = dict()):
|
|
||||||
def build_headers(self):
|
|
||||||
'''
|
|
||||||
Build Invoice Ninja API headers for request.
|
|
||||||
|
|
||||||
A header dictionary with the API token is returned by default.
|
|
||||||
'''
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'X-API-TOKEN': self.api_token,
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'}
|
|
||||||
|
|
||||||
return headers
|
|
||||||
|
|
||||||
def ping(self):
|
|
||||||
'''
|
|
||||||
Ping Invoice Ninja instance.
|
|
||||||
'''
|
|
||||||
server_response = requests.get(url=self._get_url_for(),
|
|
||||||
headers=self.headers)
|
|
||||||
|
|
||||||
if server_response.ok:
|
|
||||||
return True
|
|
||||||
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
from invoice_ninja import InvoiceNinja
|
|
||||||
|
|
||||||
class BaseEndpoint(InvoiceNinja):
|
|
||||||
def bulk(self, action: str):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def archive(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def delete(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def restore(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def all(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def download(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
from invoice_ninja.endpoints.base_endpoint import BaseEndpoint
|
|
||||||
from invoice_ninja.types.client import Client
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
class Clients(BaseEndpoint):
|
|
||||||
uri = '/api/v1/clients'
|
|
||||||
|
|
||||||
def __init__(self, base_url: str = str(), api_token: str = str()):
|
|
||||||
super().__init__(base_url, api_token)
|
|
||||||
self.url = super()._get_url_for('clients')
|
|
||||||
|
|
||||||
def __build_sort_params(self, sort: dict):
|
|
||||||
sort_params = {'sort': str()}
|
|
||||||
is_first_entry = True
|
|
||||||
for option in sort.keys():
|
|
||||||
if is_first_entry:
|
|
||||||
sort_params['sort'] += '{}|{}'.format(option, sort[option])
|
|
||||||
is_first_entry = False
|
|
||||||
|
|
||||||
else:
|
|
||||||
sort_params['sort'] += ' {}|{}'.format(option, sort[option])
|
|
||||||
|
|
||||||
return sort_params
|
|
||||||
|
|
||||||
def __client_from_dict(self, client: dict):
|
|
||||||
return Client(client_id=client['id'],
|
|
||||||
name=client['name'],
|
|
||||||
address=client['address1'],
|
|
||||||
city=client['city'],
|
|
||||||
state=client['state'],
|
|
||||||
postal_code=client['postal_code'],
|
|
||||||
phone=client['phone'],
|
|
||||||
email=client['contacts'][0]['email'],
|
|
||||||
pets=client['custom_value1'])
|
|
||||||
|
|
||||||
def __clients_from_response(self, response: requests.Response):
|
|
||||||
clients = list()
|
|
||||||
for client_dict in response.json()['data']:
|
|
||||||
clients.append(self.__client_from_dict(client_dict))
|
|
||||||
|
|
||||||
return clients
|
|
||||||
|
|
||||||
def show_client(self, client_id: str = None):
|
|
||||||
"""
|
|
||||||
Get client based on client id.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if client_id:
|
|
||||||
response = requests.get(url=self.url,
|
|
||||||
headers=super()._get_headers())
|
|
||||||
|
|
||||||
if response.ok:
|
|
||||||
return self.__client_from_dict(response.json()['data'])
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def list_clients(self, include: str = 'activities',
|
|
||||||
sort: dict = dict(), status: str = 'active',
|
|
||||||
name: str = None):
|
|
||||||
"""
|
|
||||||
Get list of clients.
|
|
||||||
"""
|
|
||||||
|
|
||||||
request_params = dict()
|
|
||||||
|
|
||||||
# Add sort parameters to request
|
|
||||||
if len(sort) > 0:
|
|
||||||
request_params.update(self.__build_sort_params(sort))
|
|
||||||
|
|
||||||
# Add include parameters to request
|
|
||||||
request_params.update({'include': include})
|
|
||||||
|
|
||||||
# Add status parameters to request
|
|
||||||
request_params.update({'status': status})
|
|
||||||
|
|
||||||
# Add name parameters to request
|
|
||||||
if name:
|
|
||||||
request_params.update({'name': name})
|
|
||||||
|
|
||||||
# Check is request should be sent with parameters
|
|
||||||
if len(request_params) > 0:
|
|
||||||
response = requests.get(url=self.url,
|
|
||||||
params=request_params,
|
|
||||||
headers=super()._get_headers())
|
|
||||||
else:
|
|
||||||
response = requests.get(url=self.url,
|
|
||||||
headers=super()._get_headers())
|
|
||||||
|
|
||||||
if response.ok:
|
|
||||||
return self.__clients_from_response(response)
|
|
||||||
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
class Client(object):
|
|
||||||
def __init__(self, client_id: int = None, name: str = None,
|
|
||||||
address: str = None, city: str = None, state: str = None,
|
|
||||||
postal_code: str = None, phone: str = None,
|
|
||||||
email: str = None, pets: str = None):
|
|
||||||
self.id = client_id
|
|
||||||
self.name = name
|
|
||||||
self.address = address
|
|
||||||
self.city = city
|
|
||||||
self.state = state
|
|
||||||
self.postal_code = postal_code
|
|
||||||
self.phone = phone
|
|
||||||
self.email = email
|
|
||||||
self.pets = pets
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return 'Client({}, {}, {}, {}, {}, {}, {}, {}, {})'.format(
|
|
||||||
self.id,
|
|
||||||
self.name,
|
|
||||||
self.address,
|
|
||||||
self.city,
|
|
||||||
self.state,
|
|
||||||
self.postal_code,
|
|
||||||
self.phone,
|
|
||||||
self.email,
|
|
||||||
self.pets)
|
|
||||||
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
class ClientContact(object):
|
|
||||||
def __init__(self, first_name: str = '',
|
|
||||||
last_name: str = '',
|
|
||||||
email: str = '',
|
|
||||||
phone: str = '',
|
|
||||||
send_email: bool = True,
|
|
||||||
custom_value1: str = '',
|
|
||||||
custom_value2: str = '',
|
|
||||||
custom_value3: str = '',
|
|
||||||
custom_value4: str = ''):
|
|
||||||
self.first_name = first_name
|
|
||||||
self.last_name = last_name
|
|
||||||
self.email = email
|
|
||||||
self.phone = phone
|
|
||||||
|
|
||||||
# Flag for whether the contact will receive emails.
|
|
||||||
self.send_email = send_email
|
|
||||||
|
|
||||||
# Custom values
|
|
||||||
self.custom_value1 = custom_value1
|
|
||||||
self.custom_value2 = custom_value2
|
|
||||||
self.custom_value3 = custom_value3
|
|
||||||
self.custom_value4 = custom_value4
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return 'ClientContact({}, {}, {}, {}, {}, {}, {}, {}, {})'.format(
|
|
||||||
self.first_name,
|
|
||||||
self.last_name,
|
|
||||||
self.email,
|
|
||||||
self.phone,
|
|
||||||
self.send_email,
|
|
||||||
self.custom_value1,
|
|
||||||
self.custom_value2,
|
|
||||||
self.custom_value3,
|
|
||||||
self.custom_value4)
|
|
||||||
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
class ClientSettings(object):
|
|
||||||
def __init__(self, language_id: str = None,
|
|
||||||
currency_id: str = None,
|
|
||||||
payment_terms: str = None,
|
|
||||||
valid_until: str = None,
|
|
||||||
default_task_rate: float = 0,
|
|
||||||
send_reminders: bool = None):
|
|
||||||
# The language ID for the client, for the full list of languages,
|
|
||||||
# please see this resource - optional
|
|
||||||
#
|
|
||||||
# https://invoiceninja.github.io/docs/statics/#languages
|
|
||||||
self.language_id = language_id
|
|
||||||
|
|
||||||
# The currency ID - optional
|
|
||||||
# See this resource for full list:
|
|
||||||
#
|
|
||||||
# https://invoiceninja.github.io/docs/statics/#currencies
|
|
||||||
self.currency_id = currency_id
|
|
||||||
|
|
||||||
# The payment terms - in days - optional
|
|
||||||
self.payment_terms = payment_terms
|
|
||||||
|
|
||||||
# The quote terms - optional
|
|
||||||
#
|
|
||||||
# How many days the quote will be valid for.
|
|
||||||
self.valid_until = valid_until
|
|
||||||
|
|
||||||
# The task rate for this client - optional
|
|
||||||
#
|
|
||||||
# A value of 0 equates to disabled.
|
|
||||||
self.default_task_rate = default_task_rate
|
|
||||||
|
|
||||||
# Whether the client will receive reminders - optional
|
|
||||||
#
|
|
||||||
# When left unset, this setting will rely on the company
|
|
||||||
# settings as the override/default
|
|
||||||
self.send_reminders = send_reminders
|
|
||||||
|
|
||||||
4
pyinvoiceninja/__init__.py
Normal file
4
pyinvoiceninja/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from .client import InvoiceNinjaClient
|
||||||
|
|
||||||
|
__all__ = ['InvoiceNinjaClient']
|
||||||
|
|
||||||
146
pyinvoiceninja/client.py
Normal file
146
pyinvoiceninja/client.py
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
# HTTP client
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Logger
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
# Local library imports
|
||||||
|
from .endpoints.clients import ClientsAPI
|
||||||
|
from .endpoints.documents import DocumentsAPI
|
||||||
|
|
||||||
|
class InvoiceNinjaClient(object):
|
||||||
|
"""
|
||||||
|
A client for interacting with the Invoice Ninja API.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
base_url : str
|
||||||
|
URL to Invoice Ninja instance.
|
||||||
|
api_token : str
|
||||||
|
Token for API request access.
|
||||||
|
clients : ClientsAPI
|
||||||
|
An instance of the ClientsAPI class for client-related operations.
|
||||||
|
documents : DocumentsAPI
|
||||||
|
An instance of the DocumentsAPI class for document-related operations.
|
||||||
|
debug : bool
|
||||||
|
Flag to enable or disable debug logging.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
base_url: str = 'https://invoicing.co/api/v1',
|
||||||
|
api_token: str = '',
|
||||||
|
debug: bool = False):
|
||||||
|
"""
|
||||||
|
Constructs all the necessary attributes for the InvoiceNinjaClient object
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
base_url : str, optional
|
||||||
|
The base URL of the Invoice Ninja API (default is 'https://invoicing.co/api/v1').
|
||||||
|
api_token : str, optional
|
||||||
|
The API token for authentication (default is an empty string).
|
||||||
|
debug : bool, optional
|
||||||
|
Flag to enable or disable debug logging (default is False).
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check parameters for correct types
|
||||||
|
if not isinstance(base_url, str):
|
||||||
|
raise ValueError('Base URL must be a string.')
|
||||||
|
if not isinstance(api_token, str):
|
||||||
|
raise ValueError('API token must be a string.')
|
||||||
|
if not isinstance(debug, bool):
|
||||||
|
raise ValueError('Debug flag must be a boolean.')
|
||||||
|
|
||||||
|
# Initialize paramater attributes
|
||||||
|
self._base_url = base_url
|
||||||
|
self._api_token = api_token
|
||||||
|
self._debug = debug
|
||||||
|
|
||||||
|
# Initialize API client attributes
|
||||||
|
self._documents = DocumentsAPI(self)
|
||||||
|
self._clients = ClientsAPI(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def base_url(self) -> str:
|
||||||
|
"""Gets the base URL of the Invoice Ninja API."""
|
||||||
|
return self._base_url
|
||||||
|
|
||||||
|
@base_url.setter
|
||||||
|
def base_url(self, base_url: str):
|
||||||
|
"""Sets the base URL of the Invoice Ninja API."""
|
||||||
|
if not isinstance(base_url, str):
|
||||||
|
raise ValueError('Base URL must be a string.')
|
||||||
|
self._base_url = base_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_token(self) -> str:
|
||||||
|
"""Gets the API token."""
|
||||||
|
return self._api_token
|
||||||
|
|
||||||
|
@api_token.setter
|
||||||
|
def api_token(self, api_token: str):
|
||||||
|
"""Sets the API token."""
|
||||||
|
if not isinstance(api_token, str):
|
||||||
|
raise ValueError('API token must be a string.')
|
||||||
|
self._api_token = api_token
|
||||||
|
|
||||||
|
@property
|
||||||
|
def clients(self) -> ClientsAPI:
|
||||||
|
return self._clients
|
||||||
|
|
||||||
|
@property
|
||||||
|
def documents(self) -> DocumentsAPI:
|
||||||
|
return self._documents
|
||||||
|
|
||||||
|
@property
|
||||||
|
def debug(self):
|
||||||
|
return self._debug
|
||||||
|
|
||||||
|
@debug.setter
|
||||||
|
def debug(self, debug: bool) -> None:
|
||||||
|
"""Sets the debug flag."""
|
||||||
|
if not isinstance(debug, bool):
|
||||||
|
raise ValueError('Debug flag must be a boolean.')
|
||||||
|
self._debug = debug
|
||||||
|
if self._debug:
|
||||||
|
logger.enable(__name__)
|
||||||
|
else:
|
||||||
|
logger.disable(__name__)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def headers(self) -> Dict[str, Any]:
|
||||||
|
"""Gets the headers for API requests."""
|
||||||
|
return {'X-API-TOKEN': self._api_token,
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'}
|
||||||
|
|
||||||
|
def _log_debug(self, message: str) -> None:
|
||||||
|
"""
|
||||||
|
Logs a debug message if debugging is enabled.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
message : str
|
||||||
|
The debug message to log.
|
||||||
|
"""
|
||||||
|
if self._debug:
|
||||||
|
print(f'DEBUG: {message}')
|
||||||
|
|
||||||
|
def ping(self):
|
||||||
|
"""
|
||||||
|
Ping the Invoice Ninja instance to check if it's reachable.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True if the server response is OK, False otherwise.
|
||||||
|
"""
|
||||||
|
logger.debug(f'Pinging {self.base_url}/ping with headers {self.headers}')
|
||||||
|
server_response = requests.get(url=f'{self.base_url}/ping',
|
||||||
|
headers=self.headers)
|
||||||
|
logger.debug(f'Server response: {server_response.status_code}, {server_response.text}')
|
||||||
|
|
||||||
|
return server_response.ok
|
||||||
|
|
||||||
5
pyinvoiceninja/endpoints/__init__.py
Normal file
5
pyinvoiceninja/endpoints/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from .clients import ClientsAPI
|
||||||
|
from .documents import DocumentsAPI
|
||||||
|
|
||||||
|
__all__ = ['ClientsAPI', 'DocumentsAPI']
|
||||||
|
|
||||||
176
pyinvoiceninja/endpoints/clients.py
Normal file
176
pyinvoiceninja/endpoints/clients.py
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
# HTTP client
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Logger
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from pyinvoiceninja.models import Client
|
||||||
|
|
||||||
|
class ClientsAPI:
|
||||||
|
"""
|
||||||
|
A client for interacting with the Invoice Ninja clients API.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
client : InvoiceNinjaClient
|
||||||
|
An instance of the InvoiceNinjaClient class.
|
||||||
|
clients_url : str
|
||||||
|
The URL for accessing the clients endpoint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
VALID_SORT_BY: List[str] = [
|
||||||
|
'id', 'name', 'balance', 'paid_to_date', 'payment_balance',
|
||||||
|
'credit_balance', 'archived_at', 'updated_at', 'created_at',
|
||||||
|
'display_name', 'address', 'city', 'state', 'postal_code',
|
||||||
|
'custom_value1', 'custom_value2', 'custom_value3', 'custom_value4'
|
||||||
|
]
|
||||||
|
VALID_SORT_ORDER: List[str] = ['asc', 'desc']
|
||||||
|
VALID_INCLUDES: List[str] = ['activities', 'ledger', 'system_logs']
|
||||||
|
VALID_CLIENT_STATUSES: List[str] = ['active', 'archived', 'deleted']
|
||||||
|
VALID_BALANCE_OPERATORS: List[str] = ['lt', 'lte', 'gt', 'gte', 'eq']
|
||||||
|
|
||||||
|
def __init__(self, client: 'InvoiceNinjaClient') -> None:
|
||||||
|
"""
|
||||||
|
Constructs all the necessary attributes for the ClientsAPI object.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
client : InvoiceNinjaClient
|
||||||
|
An instance of the InvoiceNinjaClient class.
|
||||||
|
"""
|
||||||
|
self.client = client
|
||||||
|
self.clients_url = f'{self.client.base_url}/clients'
|
||||||
|
|
||||||
|
def get_clients(self,
|
||||||
|
includes: Optional[List[str]] = None,
|
||||||
|
sort: Optional[Dict[str, str]] = None,
|
||||||
|
statuses: Optional[List[str]] = None,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
email: Optional[str] = None,
|
||||||
|
balance: Optional[Dict[str, Union[str, int]]] = None,
|
||||||
|
is_deleted: Optional[bool] = None,
|
||||||
|
filter_deleted_clients: Optional[bool] = None) -> List[Client]:
|
||||||
|
"""
|
||||||
|
Retrieves a list of clients from the Invoice Ninja API.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
includes : Optional[List[str]]
|
||||||
|
The child relationships to include in the response.
|
||||||
|
sort : Optional[Dict[str, str]]
|
||||||
|
A dictionary containing the field by which to sort the clients and the order.
|
||||||
|
statuses : Optional[List[str]]
|
||||||
|
The statuses to filter the clients.
|
||||||
|
name : Optional[str]
|
||||||
|
The name to filter the clients.
|
||||||
|
email : Optional[str]
|
||||||
|
The email to filter the clients.
|
||||||
|
balance : Optional[Dict[str, Union[str, int]]]
|
||||||
|
The balance to filter the clients.
|
||||||
|
is_deleted : Optional[bool]
|
||||||
|
Whether to include deleted clients.
|
||||||
|
filter_deleted_clients : Optional[bool]
|
||||||
|
Whether to filter out deleted clients.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
List[Client]
|
||||||
|
A list of Client objects if the request is successful.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
ValueError
|
||||||
|
If any of the parameters are invalid.
|
||||||
|
"""
|
||||||
|
params = {}
|
||||||
|
|
||||||
|
if includes:
|
||||||
|
if isinstance(includes, list):
|
||||||
|
for item in includes:
|
||||||
|
if item not in self.VALID_INCLUDES:
|
||||||
|
raise ValueError(f'Invalid include option: {item}. Valid options: {self.VALID_INCLUDES}')
|
||||||
|
params['include'] = ','.join(includes)
|
||||||
|
else:
|
||||||
|
raise ValueError('Includes must be a list of valid client child relationship strings.')
|
||||||
|
|
||||||
|
if sort:
|
||||||
|
if isinstance(sort, dict):
|
||||||
|
if 'sort_by' not in sort or sort['sort_by'] not in self.VALID_SORT_BY:
|
||||||
|
raise ValueError(f'Invalid sort by option: {sort["sort_by"]}. Valid options: {self.VALID_SORT_BY}')
|
||||||
|
if 'sort_order' not in sort or sort['sort_order'] not in self.VALID_SORT_ORDER:
|
||||||
|
raise ValueError(f'Invalid sort order option: {sort["sort_order"]}. Valid options: {self.VALID_SORT_ORDER}')
|
||||||
|
params['sort'] = f'{sort["sort_by"]}|{sort["sort_order"]}'
|
||||||
|
else:
|
||||||
|
raise ValueError('Sort must be a dictionary with "sort_by" and "sort_order" keys.')
|
||||||
|
|
||||||
|
if statuses:
|
||||||
|
if isinstance(statuses, list):
|
||||||
|
for item in statuses:
|
||||||
|
if item not in self.VALID_CLIENT_STATUSES:
|
||||||
|
raise ValueError(f'Invalid status: {item}. Valid options: {self.VALID_CLIENT_STATUSES}')
|
||||||
|
params['status'] = ','.join(statuses)
|
||||||
|
else:
|
||||||
|
raise ValueError('Statuses must be a list of valid client statuses.')
|
||||||
|
|
||||||
|
if name:
|
||||||
|
if isinstance(name, str):
|
||||||
|
params['name'] = name
|
||||||
|
else:
|
||||||
|
raise ValueError('Name must be a string.')
|
||||||
|
|
||||||
|
if email:
|
||||||
|
if isinstance(email, str):
|
||||||
|
params['email'] = email
|
||||||
|
else:
|
||||||
|
raise ValueError('Email parameter must be a string value.')
|
||||||
|
|
||||||
|
# FIXME: This doesn't seem to be working properly.
|
||||||
|
if balance:
|
||||||
|
if isinstance(balance, dict):
|
||||||
|
if 'operator' in balance and 'value' in balance:
|
||||||
|
if balance['operator'] not in self.VALID_BALANCE_OPERATORS:
|
||||||
|
raise ValueError(f'Invalid balance operator: {balance["operator"]}. Valid options: {self.VALID_BALANCE_OPERATORS}')
|
||||||
|
if not isinstance(balance['value'], int):
|
||||||
|
raise ValueError('Balance value must be an integer.')
|
||||||
|
params['balance'] = f'{balance["operator"]}:{balance["value"]}'
|
||||||
|
else:
|
||||||
|
raise ValueError('Balance must be a dictionary with "operator" and "value" keys.')
|
||||||
|
else:
|
||||||
|
raise ValueError('Balance must be a dictionary with "operator" and "value" keys.')
|
||||||
|
|
||||||
|
if is_deleted is not None:
|
||||||
|
if isinstance(is_deleted, bool):
|
||||||
|
params['is_deleted'] = is_deleted
|
||||||
|
else:
|
||||||
|
raise ValueError('Is deleted must be a boolean value.')
|
||||||
|
|
||||||
|
if filter_deleted_clients is not None:
|
||||||
|
if isinstance(filter_deleted_clients, bool):
|
||||||
|
params['filter_deleted_clients'] = filter_deleted_clients
|
||||||
|
else:
|
||||||
|
raise ValueError('Filter deleted clients must be a boolean value.')
|
||||||
|
|
||||||
|
request = requests.Request('GET', self.clients_url, headers=self.client.headers, params=params)
|
||||||
|
prepared_request = request.prepare()
|
||||||
|
logger.debug(f'Fetching clients from server: {prepared_request.url}')
|
||||||
|
session = requests.Session()
|
||||||
|
response = session.send(prepared_request)
|
||||||
|
logger.debug(f'Server response status: {response.status_code}')
|
||||||
|
|
||||||
|
if response.ok:
|
||||||
|
clients = [Client.from_json(client_dict) for client_dict in response.json().get('data', [])]
|
||||||
|
if clients:
|
||||||
|
logger.success('Fetched client list from server.')
|
||||||
|
return clients
|
||||||
|
else:
|
||||||
|
logger.error('Search yielded no clients.')
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
logger.error(f'Error fetching clients: {response.text}')
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
64
pyinvoiceninja/endpoints/documents.py
Normal file
64
pyinvoiceninja/endpoints/documents.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
from pyinvoiceninja.models import Client
|
||||||
|
from pyinvoiceninja.models import Document
|
||||||
|
|
||||||
|
class DocumentsAPI(object):
|
||||||
|
"""
|
||||||
|
A client for interacting with the Invoice Ninja documents API.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
client : InvoiceNinjaClient
|
||||||
|
An instance of the InvoiceNinjaClient class.
|
||||||
|
documents_url : str
|
||||||
|
The URL for accessing the clients endpoint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, client):
|
||||||
|
"""
|
||||||
|
Constructs all the necessary attributes for the DocumentsAPI object.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
client : InvoiceNinjaClient
|
||||||
|
An instance of the InvoiceNinjaClient class.
|
||||||
|
"""
|
||||||
|
self.client = client
|
||||||
|
self.documents_url = f'{self.client.base_url}/documents'
|
||||||
|
|
||||||
|
def __get_documents_from_response(self, response: requests.Response):
|
||||||
|
"""
|
||||||
|
Parses the response from the API and converts it to a list of Client objects.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
response : requests.Response
|
||||||
|
The response object from the API request.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list of Client
|
||||||
|
A list of Client objects parsed from the response.
|
||||||
|
"""
|
||||||
|
documents = list()
|
||||||
|
for document_dict in response.json()['data']:
|
||||||
|
documents.append(Document.from_json(document_dict))
|
||||||
|
return documents
|
||||||
|
|
||||||
|
def get_documents(self):
|
||||||
|
"""
|
||||||
|
Retrieves a list of documents from the Invoice Ninja API.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list of Documents
|
||||||
|
A list of Document objects if the request is successful.
|
||||||
|
"""
|
||||||
|
self.client._log_debug(f'Fetching documents from {self.documents_url}')
|
||||||
|
response = requests.get(self.documents_url, headers=self.client.headers)
|
||||||
|
self.client._log_debug(f'Server response: {response.status_code}, {response.text}')
|
||||||
|
|
||||||
|
if response.ok:
|
||||||
|
return self.__get_documents_from_response(response)
|
||||||
|
|
||||||
7
pyinvoiceninja/models/__init__.py
Normal file
7
pyinvoiceninja/models/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from .client import Client
|
||||||
|
from .clientContact import ClientContact
|
||||||
|
from .clientSettings import ClientSettings
|
||||||
|
from .document import Document
|
||||||
|
|
||||||
|
__all__ = ['Client', 'ClientContact', 'ClientSettings', 'Document']
|
||||||
|
|
||||||
402
pyinvoiceninja/models/client.py
Normal file
402
pyinvoiceninja/models/client.py
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Type
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
# JSON serializer/deserializer
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Local library imports
|
||||||
|
from pyinvoiceninja.models.clientContact import ClientContact
|
||||||
|
from pyinvoiceninja.models.clientInvoice import ClientInvoice
|
||||||
|
from pyinvoiceninja.models.clientPayment import ClientPayment
|
||||||
|
from pyinvoiceninja.models.clientSettings import ClientSettings
|
||||||
|
from pyinvoiceninja.models.document import Document
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
"""
|
||||||
|
A class to represent a client.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
id (int): The ID of the client.
|
||||||
|
name (str): The name of the client.
|
||||||
|
private_notes (str): Private notes about the client.
|
||||||
|
public_notes (str): Public notes about the client.
|
||||||
|
balance (int): The balance of the client.
|
||||||
|
paid_to_date (int): The amount paid to date by the client.
|
||||||
|
payment_balance (int): The payment balance of the client.
|
||||||
|
credit_balance (int): The credit balance of the client.
|
||||||
|
settings (dict): Client-specific settings.
|
||||||
|
is_deleted (bool): Indicates if the client is deleted.
|
||||||
|
updated_at (int): The timestamp when the client was last updated.
|
||||||
|
archived_at (int): The timestamp when the client was archived.
|
||||||
|
created_at (int): The timestamp when the client was created.
|
||||||
|
display_name (str): The display name of the client.
|
||||||
|
contacts (list): A list of contacts associated with the client.
|
||||||
|
documents (list): A list of documents associated with the client.
|
||||||
|
address (str): The address of the client.
|
||||||
|
city (str): The city of the client.
|
||||||
|
state (str): The state of the client.
|
||||||
|
postal_code (str): The postal code of the client.
|
||||||
|
phone (str): The phone number of the client.
|
||||||
|
custom_value1 (str): Custom field 1.
|
||||||
|
custom_value2 (str): Custom field 2.
|
||||||
|
custom_value3 (str): Custom field 3.
|
||||||
|
custom_value4 (str): Custom field 4.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, client_id: int = 0,
|
||||||
|
name: str = '',
|
||||||
|
private_notes: str = '',
|
||||||
|
public_notes: str = '',
|
||||||
|
balance: int = 0,
|
||||||
|
paid_to_date: int = 0,
|
||||||
|
payment_balance: int = 0,
|
||||||
|
credit_balance: int = 0,
|
||||||
|
settings: Optional[ClientSettings] = None,
|
||||||
|
is_deleted: bool = False,
|
||||||
|
updated_at: int = 0,
|
||||||
|
archived_at: int = 0,
|
||||||
|
created_at: int = 0,
|
||||||
|
display_name: str = '',
|
||||||
|
contacts: Optional[List[ClientContact]] = None,
|
||||||
|
documents: Optional[List[Document]] = None,
|
||||||
|
address: str = '',
|
||||||
|
city: str = '',
|
||||||
|
state: str = '',
|
||||||
|
postal_code: str = '',
|
||||||
|
phone: str = '',
|
||||||
|
custom_value1: str = '',
|
||||||
|
custom_value2: str = '',
|
||||||
|
custom_value3: str = '',
|
||||||
|
custom_value4: str = '',
|
||||||
|
ledger: Optional[List[Union[ClientPayment, ClientInvoice]]] = None):
|
||||||
|
self._id = client_id
|
||||||
|
self._name = name
|
||||||
|
self._private_notes = private_notes
|
||||||
|
self._public_notes = public_notes
|
||||||
|
self._balance = balance
|
||||||
|
self._paid_to_date = paid_to_date
|
||||||
|
self._payment_balance = payment_balance
|
||||||
|
self._credit_balance = credit_balance
|
||||||
|
self._settings = settings or {}
|
||||||
|
self._is_deleted = is_deleted
|
||||||
|
self._updated_at = updated_at
|
||||||
|
self._archived_at = archived_at
|
||||||
|
self._created_at = created_at
|
||||||
|
self._display_name = display_name
|
||||||
|
self._contacts = contacts or []
|
||||||
|
self._documents = documents or []
|
||||||
|
self._address = address
|
||||||
|
self._city = city
|
||||||
|
self._state = state
|
||||||
|
self._postal_code = postal_code
|
||||||
|
self._phone = phone
|
||||||
|
self._custom_value1 = custom_value1
|
||||||
|
self._custom_value2 = custom_value2
|
||||||
|
self._custom_value3 = custom_value3
|
||||||
|
self._custom_value4 = custom_value4
|
||||||
|
self._ledger = ledger
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self):
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@name.setter
|
||||||
|
def name(self, name):
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
@name.deleter
|
||||||
|
def name(self):
|
||||||
|
self._name = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def private_notes(self):
|
||||||
|
return self._private_notes
|
||||||
|
|
||||||
|
@private_notes.setter
|
||||||
|
def private_notes(self, note):
|
||||||
|
self._private_notes = note
|
||||||
|
|
||||||
|
@private_notes.deleter
|
||||||
|
def private_notes(self):
|
||||||
|
self._private_notes = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def public_notes(self):
|
||||||
|
return self._public_notes
|
||||||
|
|
||||||
|
@public_notes.setter
|
||||||
|
def public_notes(self, note):
|
||||||
|
self._public_notes = note
|
||||||
|
|
||||||
|
@public_notes.deleter
|
||||||
|
def public_notes(self):
|
||||||
|
self._public_notes = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def balance(self):
|
||||||
|
return self._balance
|
||||||
|
|
||||||
|
@property
|
||||||
|
def paid_to_date(self):
|
||||||
|
return self._paid_to_date
|
||||||
|
|
||||||
|
@property
|
||||||
|
def payment_balance(self):
|
||||||
|
return self._payment_balance
|
||||||
|
|
||||||
|
@property
|
||||||
|
def credit_balance(self):
|
||||||
|
return self._credit_balance
|
||||||
|
|
||||||
|
@property
|
||||||
|
def settings(self):
|
||||||
|
return self._settings
|
||||||
|
|
||||||
|
@settings.setter
|
||||||
|
def settings(self, settings):
|
||||||
|
self._settings = settings
|
||||||
|
|
||||||
|
@settings.deleter
|
||||||
|
def settings(self):
|
||||||
|
self._settings = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_deleted(self):
|
||||||
|
return self._is_deleted
|
||||||
|
|
||||||
|
@property
|
||||||
|
def updated_at(self):
|
||||||
|
return self._updated_at
|
||||||
|
|
||||||
|
@property
|
||||||
|
def archived_at(self):
|
||||||
|
return self._archived_at
|
||||||
|
|
||||||
|
@property
|
||||||
|
def created_at(self):
|
||||||
|
return self._created_at
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_name(self):
|
||||||
|
return self._display_name
|
||||||
|
|
||||||
|
@display_name.setter
|
||||||
|
def display_name(self, name):
|
||||||
|
self._display_name = name
|
||||||
|
|
||||||
|
@display_name.deleter
|
||||||
|
def display_name(self):
|
||||||
|
self._display_name = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def contacts(self):
|
||||||
|
return self._contacts
|
||||||
|
|
||||||
|
@contacts.setter
|
||||||
|
def contacts(self, contacts):
|
||||||
|
self._contacts = contacts
|
||||||
|
|
||||||
|
@contacts.deleter
|
||||||
|
def contacts(self):
|
||||||
|
self._contacts = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def documents(self):
|
||||||
|
return self._documents
|
||||||
|
|
||||||
|
@documents.setter
|
||||||
|
def documents(self, documents):
|
||||||
|
self._documents = documents
|
||||||
|
|
||||||
|
@documents.deleter
|
||||||
|
def documents(self):
|
||||||
|
self._documents = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def address(self):
|
||||||
|
return self._address
|
||||||
|
|
||||||
|
@address.setter
|
||||||
|
def address(self, address):
|
||||||
|
self._address = address
|
||||||
|
|
||||||
|
@address.deleter
|
||||||
|
def address(self):
|
||||||
|
self._address = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def city(self):
|
||||||
|
return self._city
|
||||||
|
|
||||||
|
@city.setter
|
||||||
|
def city(self, city):
|
||||||
|
self._city = city
|
||||||
|
|
||||||
|
@city.deleter
|
||||||
|
def city(self):
|
||||||
|
self._city = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@state.setter
|
||||||
|
def state(self, state):
|
||||||
|
self._state = state
|
||||||
|
|
||||||
|
@state.deleter
|
||||||
|
def state(self):
|
||||||
|
self._state = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def postal_code(self):
|
||||||
|
return self._postal_code
|
||||||
|
|
||||||
|
@postal_code.setter
|
||||||
|
def postal_code(self, postal_code):
|
||||||
|
self._postal_code = postal_code
|
||||||
|
|
||||||
|
@postal_code.deleter
|
||||||
|
def postal_code(self):
|
||||||
|
self._postal_code = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def custom_value1(self):
|
||||||
|
return self._custom_value1
|
||||||
|
|
||||||
|
@custom_value1.setter
|
||||||
|
def custom_value1(self, value):
|
||||||
|
self._custom_value1 = value
|
||||||
|
|
||||||
|
@custom_value1.deleter
|
||||||
|
def custom_value1(self):
|
||||||
|
self._custom_value1 = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def custom_value2(self):
|
||||||
|
return self._custom_value2
|
||||||
|
|
||||||
|
@custom_value2.setter
|
||||||
|
def custom_value2(self, value):
|
||||||
|
self._custom_value2 = value
|
||||||
|
|
||||||
|
@custom_value2.deleter
|
||||||
|
def custom_value2(self):
|
||||||
|
self._custom_value2 = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def custom_value3(self):
|
||||||
|
return self._custom_value3
|
||||||
|
|
||||||
|
@custom_value3.setter
|
||||||
|
def custom_value3(self, value):
|
||||||
|
self._custom_value3 = value
|
||||||
|
|
||||||
|
@custom_value3.deleter
|
||||||
|
def custom_value3(self):
|
||||||
|
self._custom_value3 = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def custom_value4(self):
|
||||||
|
return self._custom_value4
|
||||||
|
|
||||||
|
@custom_value4.setter
|
||||||
|
def custom_value4(self, value):
|
||||||
|
self._custom_value4 = value
|
||||||
|
|
||||||
|
@custom_value4.deleter
|
||||||
|
def custom_value4(self):
|
||||||
|
self._custom_value4 = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ledger(self):
|
||||||
|
return self._ledger
|
||||||
|
|
||||||
|
@ledger.setter
|
||||||
|
def ledger(self, ledger: List[Dict[str, Any]]):
|
||||||
|
self._ledger = ledger
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, client: dict):
|
||||||
|
"""
|
||||||
|
Create a Client instance from a JSON-like dictionary.
|
||||||
|
"""
|
||||||
|
contacts = [ClientContact.from_json(contact) for contact in client.get('contacts', [])]
|
||||||
|
documents = [Document.from_json(document) for document in client.get('documents', [])]
|
||||||
|
ledger_processed = []
|
||||||
|
for index, item in enumerate(client.get('ledger', [])):
|
||||||
|
if index % 2 == 0:
|
||||||
|
# Processing ClientInvoice
|
||||||
|
ledger_processed = ClientInvoice.from_json(item)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Processing ClientPayment
|
||||||
|
ledger_processed = ClientPayment.from_json(item)
|
||||||
|
|
||||||
|
|
||||||
|
return cls(client_id=client['id'],
|
||||||
|
name=client['name'],
|
||||||
|
private_notes=client['private_notes'],
|
||||||
|
public_notes=client['public_notes'],
|
||||||
|
balance=client['balance'],
|
||||||
|
paid_to_date=client['paid_to_date'],
|
||||||
|
payment_balance=client['payment_balance'],
|
||||||
|
credit_balance=client['credit_balance'],
|
||||||
|
settings=ClientSettings.from_json(client['settings']),
|
||||||
|
is_deleted=client['is_deleted'],
|
||||||
|
updated_at=client['updated_at'],
|
||||||
|
archived_at=client['archived_at'],
|
||||||
|
created_at=client['created_at'],
|
||||||
|
display_name=client['display_name'],
|
||||||
|
contacts=contacts,
|
||||||
|
documents=documents,
|
||||||
|
address=client['address1'],
|
||||||
|
city=client['city'],
|
||||||
|
state=client['state'],
|
||||||
|
postal_code=client['postal_code'],
|
||||||
|
phone=client['phone'],
|
||||||
|
custom_value1=client['custom_value1'],
|
||||||
|
custom_value2=client['custom_value2'],
|
||||||
|
custom_value3=client['custom_value3'],
|
||||||
|
custom_value4=client['custom_value4'],
|
||||||
|
ledger=ledger_processed)
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
"""
|
||||||
|
Convert the Client instance to a JSON-like dictionary.
|
||||||
|
"""
|
||||||
|
return json.dumps({'id': self._id,
|
||||||
|
'name': self._name,
|
||||||
|
'private_notes': self._private_notes,
|
||||||
|
'public_notes': self._public_notes,
|
||||||
|
'balance': self._balance,
|
||||||
|
'paid_to_date': self._paid_to_date,
|
||||||
|
'payment_balance': self._payment_balance,
|
||||||
|
'credit_balance': self._credit_balance,
|
||||||
|
'settings': self._settings,
|
||||||
|
'is_deleted': self._is_deleted,
|
||||||
|
'updated_at': self._updated_at,
|
||||||
|
'archived_at': self._archived_at,
|
||||||
|
'created_at': self._created_at,
|
||||||
|
'display_name': self._display_name,
|
||||||
|
'contacts': [contact.to_json() for contact in self._contacts],
|
||||||
|
'documents': [document.to_json() for document in self._documents],
|
||||||
|
'address1': self._address,
|
||||||
|
'city': self._city,
|
||||||
|
'state': self._state,
|
||||||
|
'postal_code': self._postal_code,
|
||||||
|
'phone': self._phone,
|
||||||
|
'custom_value1': self._custom_value1,
|
||||||
|
'custom_value2': self._custom_value2,
|
||||||
|
'custom_value3': self._custom_value3,
|
||||||
|
'custom_value4': self._custom_value4,
|
||||||
|
'ledger': self._ledger})
|
||||||
|
|
||||||
254
pyinvoiceninja/models/clientContact.py
Normal file
254
pyinvoiceninja/models/clientContact.py
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
class ClientContact(object):
|
||||||
|
def __init__(self, contact_id: str = '',
|
||||||
|
first_name: str = '',
|
||||||
|
last_name: str = '',
|
||||||
|
email: str = '',
|
||||||
|
phone: str = '',
|
||||||
|
password: str = '',
|
||||||
|
created_at: int = 0,
|
||||||
|
updated_at: int = 0,
|
||||||
|
archived_at: int = 0,
|
||||||
|
is_primary: bool = False,
|
||||||
|
is_locked: bool = False,
|
||||||
|
send_email: bool = False,
|
||||||
|
contact_key: str = '',
|
||||||
|
last_login: int = 0,
|
||||||
|
portal_link: str = '',
|
||||||
|
custom_value1: str = '',
|
||||||
|
custom_value2: str = '',
|
||||||
|
custom_value3: str = '',
|
||||||
|
custom_value4: str = '') -> None:
|
||||||
|
self._contact_id = contact_id
|
||||||
|
self._first_name = first_name
|
||||||
|
self._last_name = last_name
|
||||||
|
self._email = email
|
||||||
|
self._phone = phone
|
||||||
|
self._password = password
|
||||||
|
self._created_at = created_at
|
||||||
|
self._updated_at = updated_at
|
||||||
|
self._archived_at = archived_at
|
||||||
|
self._is_primary = is_primary
|
||||||
|
self._is_locked = is_locked
|
||||||
|
self._contact_key = contact_key
|
||||||
|
self._last_login = last_login
|
||||||
|
self._send_email = send_email
|
||||||
|
self._portal_link = portal_link
|
||||||
|
self._custom_value1 = custom_value1
|
||||||
|
self._custom_value2 = custom_value2
|
||||||
|
self._custom_value3 = custom_value3
|
||||||
|
self._custom_value4 = custom_value4
|
||||||
|
|
||||||
|
@property
|
||||||
|
def contact_id(self) -> str:
|
||||||
|
return self._contact_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def first_name(self) -> str:
|
||||||
|
return self._first_name
|
||||||
|
|
||||||
|
@first_name.setter
|
||||||
|
def first_name(self, first_name: str) -> None:
|
||||||
|
self._first_name = first_name
|
||||||
|
|
||||||
|
@first_name.deleter
|
||||||
|
def first_name(self) -> None:
|
||||||
|
self._first_name = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_name(self) -> str:
|
||||||
|
return self._last_name
|
||||||
|
|
||||||
|
@last_name.setter
|
||||||
|
def last_name(self, last_name: str) -> None:
|
||||||
|
self._last_name = last_name
|
||||||
|
|
||||||
|
@last_name.deleter
|
||||||
|
def last_name(self) -> None:
|
||||||
|
self._last_name = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def email(self) -> str:
|
||||||
|
return self._email
|
||||||
|
|
||||||
|
@email.setter
|
||||||
|
def email(self, email: str) -> None:
|
||||||
|
self._email = email
|
||||||
|
|
||||||
|
@email.deleter
|
||||||
|
def email(self) -> None:
|
||||||
|
self._email = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def phone(self) -> str:
|
||||||
|
return self._phone
|
||||||
|
|
||||||
|
@phone.setter
|
||||||
|
def phone(self, phone: str) -> None:
|
||||||
|
self._phone = phone
|
||||||
|
|
||||||
|
@phone.deleter
|
||||||
|
def phone(self) -> None:
|
||||||
|
self._phone = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def password(self) -> str:
|
||||||
|
return self._password
|
||||||
|
|
||||||
|
@password.setter
|
||||||
|
def password(self, password: str) -> None:
|
||||||
|
self._password = password
|
||||||
|
|
||||||
|
@password.deleter
|
||||||
|
def password(self) -> None:
|
||||||
|
self._password = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def created_at(self) -> int:
|
||||||
|
return self._created_at
|
||||||
|
|
||||||
|
@property
|
||||||
|
def updated_at(self) -> int:
|
||||||
|
return self._updated_at
|
||||||
|
|
||||||
|
@property
|
||||||
|
def archived_at(self) -> int:
|
||||||
|
return self._archived_at
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_primary(self) -> bool:
|
||||||
|
return self._is_primary
|
||||||
|
|
||||||
|
@is_primary.setter
|
||||||
|
def is_primary(self, is_primary: bool) -> None:
|
||||||
|
self._is_primary = is_primary
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_locked(self) -> bool:
|
||||||
|
return self._is_locked
|
||||||
|
|
||||||
|
@is_locked.setter
|
||||||
|
def is_locked(self, is_locked: bool) -> None:
|
||||||
|
self._is_locked = is_locked
|
||||||
|
|
||||||
|
@property
|
||||||
|
def contact_key(self) -> str:
|
||||||
|
return self._contact_key
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_login(self) -> int:
|
||||||
|
return self._last_login
|
||||||
|
|
||||||
|
@property
|
||||||
|
def send_email(self) -> bool:
|
||||||
|
return self._send_email
|
||||||
|
|
||||||
|
@send_email.setter
|
||||||
|
def send_email(self, send_email: bool) -> None:
|
||||||
|
self._send_email = send_email
|
||||||
|
|
||||||
|
@property
|
||||||
|
def portal_link(self) -> str:
|
||||||
|
return self._portal_link
|
||||||
|
|
||||||
|
@property
|
||||||
|
def custom_value1(self) -> str:
|
||||||
|
return self._custom_value1
|
||||||
|
|
||||||
|
@custom_value1.setter
|
||||||
|
def custom_value1(self, value: str) -> None:
|
||||||
|
self._custom_value1 = value
|
||||||
|
|
||||||
|
@custom_value1.deleter
|
||||||
|
def custom_value1(self) -> None:
|
||||||
|
self._custom_value1 = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def custom_value2(self) -> str:
|
||||||
|
return self._custom_value2
|
||||||
|
|
||||||
|
@custom_value2.setter
|
||||||
|
def custom_value2(self, value: str) -> None:
|
||||||
|
self._custom_value2 = value
|
||||||
|
|
||||||
|
@custom_value2.deleter
|
||||||
|
def custom_value2(self) -> None:
|
||||||
|
self._custom_value2 = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def custom_value3(self) -> str:
|
||||||
|
return self._custom_value3
|
||||||
|
|
||||||
|
@custom_value3.setter
|
||||||
|
def custom_value3(self, value: str) -> None:
|
||||||
|
self._custom_value3 = value
|
||||||
|
|
||||||
|
@custom_value3.deleter
|
||||||
|
def custom_value3(self) -> None:
|
||||||
|
self._custom_value3 = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def custom_value4(self) -> str:
|
||||||
|
return self._custom_value4
|
||||||
|
|
||||||
|
@custom_value4.setter
|
||||||
|
def custom_value4(self, value: str) -> None:
|
||||||
|
self._custom_value4 = value
|
||||||
|
|
||||||
|
@custom_value4.deleter
|
||||||
|
def custom_value4(self) -> None:
|
||||||
|
self._custom_value4 = ''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, contact: Dict[str, Any]) -> 'ClientContact':
|
||||||
|
return cls(contact_id=contact['id'],
|
||||||
|
first_name=contact['first_name'],
|
||||||
|
last_name=contact['last_name'],
|
||||||
|
email=contact['email'],
|
||||||
|
created_at=contact['created_at'],
|
||||||
|
updated_at=contact['updated_at'],
|
||||||
|
archived_at=contact['archived_at'],
|
||||||
|
is_primary=contact['is_primary'],
|
||||||
|
is_locked=contact['is_locked'],
|
||||||
|
phone=contact['phone'],
|
||||||
|
custom_value1=contact['custom_value1'],
|
||||||
|
custom_value2=contact['custom_value2'],
|
||||||
|
custom_value3=contact['custom_value3'],
|
||||||
|
custom_value4=contact['custom_value4'],
|
||||||
|
contact_key=contact['contact_key'],
|
||||||
|
send_email=contact['send_email'],
|
||||||
|
last_login=contact['last_login'],
|
||||||
|
password=contact['password'],
|
||||||
|
portal_link=contact['link'])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json_list(cls, contacts: List[Dict[str, Any]]) -> List['ClientContact']:
|
||||||
|
return [cls.from_json(contact) for contact in contacts]
|
||||||
|
|
||||||
|
def to_json(self) -> str:
|
||||||
|
import json
|
||||||
|
contact = {
|
||||||
|
"contact_id": self._contact_id,
|
||||||
|
"first_name": self._first_name,
|
||||||
|
"last_name": self._last_name,
|
||||||
|
"email": self._email,
|
||||||
|
"phone": self._phone,
|
||||||
|
"created_at": self._created_at,
|
||||||
|
"updated_at": self._updated_at,
|
||||||
|
"archived_at": self._archived_at,
|
||||||
|
"is_primary": self._is_primary,
|
||||||
|
"is_locked": self._is_locked,
|
||||||
|
"send_email": self._send_email,
|
||||||
|
"contact_key": self._contact_key,
|
||||||
|
"last_login": self._last_login,
|
||||||
|
"portal_link": self._portal_link,
|
||||||
|
"custom_value1": self._custom_value1,
|
||||||
|
"custom_value2": self._custom_value2,
|
||||||
|
"custom_value3": self._custom_value3,
|
||||||
|
"custom_value4": self._custom_value4
|
||||||
|
}
|
||||||
|
return json.dumps(contact)
|
||||||
|
|
||||||
48
pyinvoiceninja/models/clientInvoice.py
Normal file
48
pyinvoiceninja/models/clientInvoice.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
class ClientInvoice:
|
||||||
|
def __init__(self, invoice_id: Optional[str] = None,
|
||||||
|
notes: Optional[str] = None,
|
||||||
|
balance: Optional[int] = None,
|
||||||
|
adjustment: Optional[int] = None,
|
||||||
|
activity_id: Optional[int] = None,
|
||||||
|
created_at: Optional[int] = None,
|
||||||
|
updated_at: Optional[int] = None,
|
||||||
|
archived_at: Optional[int] = None):
|
||||||
|
self.invoice_id = invoice_id
|
||||||
|
self.notes = notes
|
||||||
|
self.balance = balance
|
||||||
|
self.adjustment = adjustment
|
||||||
|
self.activity_id = activity_id
|
||||||
|
self.created_at = created_at
|
||||||
|
self.updated_at = updated_at
|
||||||
|
self.archived_at = archived_at
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls: Type['ClientInvoice'], invoice: Dict[str, Any]) -> 'ClientInvoice':
|
||||||
|
return cls(invoice_id=invoice.get('invoice_id', None),
|
||||||
|
notes=invoice.get('notes', None),
|
||||||
|
balance=invoice.get('balance', None),
|
||||||
|
adjustment=invoice.get('adjustment', None),
|
||||||
|
activity_id=invoice.get('activity_id', None),
|
||||||
|
created_at=invoice.get('created_at', None),
|
||||||
|
updated_at=invoice.get('updated_at', None),
|
||||||
|
archived_at=invoice.get('archived_at', None))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Invoice: {self.invoice_id}, {self.balance}'
|
||||||
|
|
||||||
|
def to_json(self) -> str:
|
||||||
|
return json.dumps({'invoice_id': self.invoice_id,
|
||||||
|
'notes': self.notes,
|
||||||
|
'balance': self.balance,
|
||||||
|
'adjustment': self.adjustment,
|
||||||
|
'activity_id': self.activity_id,
|
||||||
|
'created_at': self.created_at,
|
||||||
|
'archive_at': self.archived_at})
|
||||||
|
|
||||||
48
pyinvoiceninja/models/clientPayment.py
Normal file
48
pyinvoiceninja/models/clientPayment.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
class ClientPayment:
|
||||||
|
def __init__(self, payment_id: Optional[str] = None,
|
||||||
|
notes: Optional[str] = None,
|
||||||
|
balance: Optional[int] = None,
|
||||||
|
adjustment: Optional[int] = None,
|
||||||
|
activity_id: Optional[int] = None,
|
||||||
|
created_at: Optional[int] = None,
|
||||||
|
updated_at: Optional[int] = None,
|
||||||
|
archived_at: Optional[int] = None):
|
||||||
|
self.payment_id = payment_id
|
||||||
|
self.notes = notes
|
||||||
|
self.balance = balance
|
||||||
|
self.adjustment = adjustment
|
||||||
|
self.activity_id = activity_id
|
||||||
|
self.created_at = created_at
|
||||||
|
self.updated_at = updated_at
|
||||||
|
self.archived_at = archived_at
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls: Type['ClientLedger'], ledger: Dict[str, Any]) -> 'ClientLedger':
|
||||||
|
return cls(payment_id=ledger.get('payment_id', None),
|
||||||
|
notes=ledger.get('notes', None),
|
||||||
|
balance=ledger.get('balance', None),
|
||||||
|
adjustment=ledger.get('adjustment', None),
|
||||||
|
activity_id=ledger.get('activity_id', None),
|
||||||
|
created_at=ledger.get('created_at', None),
|
||||||
|
updated_at=ledger.get('updated_at', None),
|
||||||
|
archived_at=ledger.get('archived_at', None))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Ledger: {self.payment_id}, {self.balance}'
|
||||||
|
|
||||||
|
def to_json(self) -> str:
|
||||||
|
return json.dumps({'payment_id': self.payment_id,
|
||||||
|
'notes': self.notes,
|
||||||
|
'balance': self.balance,
|
||||||
|
'adjustment': self.adjustment,
|
||||||
|
'activity_id': self.activity_id,
|
||||||
|
'created_at': self.created_at,
|
||||||
|
'archive_at': self.archived_at})
|
||||||
|
|
||||||
42
pyinvoiceninja/models/clientSettings.py
Normal file
42
pyinvoiceninja/models/clientSettings.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
class ClientSettings:
|
||||||
|
def __init__(self, currency_id: Optional[str] = None,
|
||||||
|
language_id: Optional[str] = None,
|
||||||
|
payment_terms: Optional[int] = None,
|
||||||
|
default_task_rate: Optional[float] = None,
|
||||||
|
send_reminders: Optional[bool] = None,
|
||||||
|
valid_until: Optional[str] = None,
|
||||||
|
enable_client_portal_password: Optional[bool] = None):
|
||||||
|
self.currency_id = currency_id
|
||||||
|
self.language_id = language_id
|
||||||
|
self.payment_terms = payment_terms
|
||||||
|
self.default_task_rate = default_task_rate
|
||||||
|
self.send_reminders = send_reminders
|
||||||
|
self.valid_until = valid_until
|
||||||
|
self.enable_client_portal_password = enable_client_portal_password
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls: Type['ClientSettings'], settings: Dict[str, Any]) -> 'ClientSettings':
|
||||||
|
return cls(currency_id=settings.get('currency_id', None),
|
||||||
|
language_id=settings.get('language_id', None),
|
||||||
|
payment_terms=settings.get('payment_terms', None),
|
||||||
|
default_task_rate=settings.get('default_task_rate', None),
|
||||||
|
send_reminders=settings.get('send_reminders', None),
|
||||||
|
valid_until=settings.get('valid_until', None),
|
||||||
|
enable_client_portal_password=settings.get('enable_client_portal_password', None))
|
||||||
|
|
||||||
|
def to_json(self) -> str:
|
||||||
|
return json.dumps({'currency_id': self.currency_id,
|
||||||
|
'language_id': self.language_id,
|
||||||
|
'payment_terms': self.payment_terms,
|
||||||
|
'default_task_rate': self.default_task_rate,
|
||||||
|
'send_reminders': self.send_reminders,
|
||||||
|
'valid_until': self.valid_until,
|
||||||
|
'enable_client_portal_password': self.enable_client_portal_password})
|
||||||
|
|
||||||
147
pyinvoiceninja/models/document.py
Normal file
147
pyinvoiceninja/models/document.py
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
import json
|
||||||
|
|
||||||
|
class Document:
|
||||||
|
def __init__(self, document_id: str = '',
|
||||||
|
user_id: str = '',
|
||||||
|
assigned_user_id: str = '',
|
||||||
|
project_id: str = '',
|
||||||
|
vendor_id: str = '',
|
||||||
|
name: str = '',
|
||||||
|
url: str = '',
|
||||||
|
preview: str = '',
|
||||||
|
document_type: str = '',
|
||||||
|
disk: str = '',
|
||||||
|
document_hash: str = '',
|
||||||
|
is_deleted: bool = False,
|
||||||
|
is_default: bool = False,
|
||||||
|
created_at: int = 0,
|
||||||
|
updated_at: int = 0,
|
||||||
|
deleted_at: int = 0):
|
||||||
|
self._document_id = document_id
|
||||||
|
self._user_id = user_id
|
||||||
|
self._assigned_user_id = assigned_user_id
|
||||||
|
self._project_id = project_id
|
||||||
|
self._vendor_id = vendor_id
|
||||||
|
self._name = name
|
||||||
|
self._url = url
|
||||||
|
self._preview = preview
|
||||||
|
self._document_type = document_type
|
||||||
|
self._disk = disk
|
||||||
|
self._document_hash = document_hash
|
||||||
|
self._is_deleted = is_deleted
|
||||||
|
self._is_default = is_default
|
||||||
|
self._created_at = created_at
|
||||||
|
self._updated_at = updated_at
|
||||||
|
self._deleted_at = deleted_at
|
||||||
|
|
||||||
|
@property
|
||||||
|
def document_id(self) -> str:
|
||||||
|
return self._document_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_id(self) -> str:
|
||||||
|
return self._user_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def assigned_user_id(self) -> str:
|
||||||
|
return self._assigned_user_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def project_id(self) -> str:
|
||||||
|
return self._project_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vendor_id(self) -> str:
|
||||||
|
return self._vendor_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self) -> str:
|
||||||
|
return self._url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preview(self) -> str:
|
||||||
|
return self._preview
|
||||||
|
|
||||||
|
@property
|
||||||
|
def document_type(self) -> str:
|
||||||
|
return self._document_type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def disk(self) -> str:
|
||||||
|
return self._disk
|
||||||
|
|
||||||
|
@property
|
||||||
|
def document_hash(self) -> str:
|
||||||
|
return self._document_hash
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_deleted(self) -> bool:
|
||||||
|
return self._is_deleted
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_default(self) -> bool:
|
||||||
|
return self._is_default
|
||||||
|
|
||||||
|
@property
|
||||||
|
def created_at(self) -> int:
|
||||||
|
return self._created_at
|
||||||
|
|
||||||
|
@property
|
||||||
|
def updated_at(self) -> int:
|
||||||
|
return self._updated_at
|
||||||
|
|
||||||
|
@property
|
||||||
|
def deleted_at(self) -> int:
|
||||||
|
return self._deleted_at
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, document: Dict[str, Any]) -> 'Document':
|
||||||
|
return cls(document_id=document.get('id', ''),
|
||||||
|
user_id=document.get('user_id', ''),
|
||||||
|
assigned_user_id=document.get('assigned_user_id', ''),
|
||||||
|
project_id=document.get('project_id', ''),
|
||||||
|
vendor_id=document.get('vendor_id', ''),
|
||||||
|
name=document.get('name', ''),
|
||||||
|
url=document.get('url', ''),
|
||||||
|
preview=document.get('preview', ''),
|
||||||
|
document_type=document.get('type', ''),
|
||||||
|
disk=document.get('disk', ''),
|
||||||
|
document_hash=document.get('hash', ''),
|
||||||
|
is_deleted=document.get('is_deleted', False),
|
||||||
|
is_default=document.get('is_default', False),
|
||||||
|
created_at=document.get('created_at', 0),
|
||||||
|
updated_at=document.get('updated_at', 0),
|
||||||
|
deleted_at=document.get('deleted_at', 0))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json_list(cls, documents: List[Dict[str, Any]]) -> List['Document']:
|
||||||
|
return [cls.from_json(document) for document in documents]
|
||||||
|
|
||||||
|
def to_json(self) -> str:
|
||||||
|
return json.dumps({'document_id': self._document_id,
|
||||||
|
'user_id': self._user_id,
|
||||||
|
'assigned_user_id': self._assigned_user_id,
|
||||||
|
'project_id': self._project_id,
|
||||||
|
'vendor_id': self._vendor_id,
|
||||||
|
'name': self._name,
|
||||||
|
'url': self._url,
|
||||||
|
'preview': self._preview,
|
||||||
|
'document_type': self._document_type,
|
||||||
|
'disk': self._disk,
|
||||||
|
'document_hash': self._document_hash,
|
||||||
|
'is_deleted': self._is_deleted,
|
||||||
|
'is_default': self._is_default,
|
||||||
|
'created_at': self._created_at,
|
||||||
|
'updated_at': self._updated_at,
|
||||||
|
'deleted_at': self._deleted_at})
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Document({self._document_id}, {self._name})'
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user