Skip to content

Commit

Permalink
Delete Support added for Invoice (#8)
Browse files Browse the repository at this point in the history
* Delete Support added for Invoice

* Delete Journal, Journal LineItem, Invoice LineItem added (#9)

* Delete Journal, Journal LineItem, Invoice LineItem added

* Bulk post support invoice line item (#10)

* Bulk post support added for Invoice Line Item

* comment resolved

* comments resolved

* comments resolved

* comments resolved

* Bulk Post Support Added for Journal Line Items (#11)

* Bulk Post Support Added for Journal Line Items

* Version updated

* companyid made public
  • Loading branch information
ruuushhh authored Nov 28, 2023
1 parent 4a88a46 commit 5aca376
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 5 deletions.
120 changes: 118 additions & 2 deletions dynamics/apis/api_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class ApiBase:
def __init__(self):
self.__access_token = None
self.__server_url = None
self.__batch_url = None
self.company_id = None

def change_access_token(self, access_token):
"""Change the old access token with the new one.
Expand All @@ -34,12 +36,27 @@ def set_server_url(self, server_url):
"""
self.__server_url = server_url

def set_batch_url(self, batch_url):
"""Set the batch URL dynamically upon creating a connection
Parameters:
batch_url(str): The current batch URL
"""
self.__batch_url = batch_url

def set_company_id(self, company_id):
"""Set the company id dynamically upon creating a connection
Parameters:
company_id(str): The current company id
"""
self.company_id = company_id

def _get_request(self, params, api_url):
"""Create a HTTP GET request.
Parameters:
params (dict): HTTP GET parameters for the wanted API.
api_url (str): Url for the wanted API.
Returns:
A response from the request (dict).
Expand Down Expand Up @@ -182,4 +199,103 @@ def _patch_request(self, content_type, data, api_url):
raise InternalServerError('Internal server error', response.text)

else:
raise DynamicsError('Error: {0}'.format(response.status_code), response.text)
raise DynamicsError('Error: {0}'.format(response.status_code), response.text)

def _delete_request(self, params, api_url):
"""
"""
api_headers = {
'Authorization': self.__access_token,
'Accept': 'application/json'
}
api_params = {}

for k in params:
# ignore all unused params
if not params[k] is None:
p = params[k]

# convert boolean to lowercase string
if isinstance(p, bool):
p = str(p).lower()

api_params[k] = p

response = requests.delete(
'{0}{1}'.format(self.__server_url, api_url),
headers=api_headers,
params=api_params
)

if response.status_code == 204:
return {'status': 'success'}

elif response.status_code == 400:
raise WrongParamsError('Some of the parameters are wrong', response.text)

elif response.status_code == 401:
raise InvalidTokenError('Invalid token, try to refresh it', response.text)

elif response.status_code == 403:
raise NoPrivilegeError('Forbidden, the user has insufficient privilege', response.text)

elif response.status_code == 404:
raise NotFoundItemError('Not found item with ID', response.text)

elif response.status_code == 498:
raise ExpiredTokenError('Expired token, try to refresh it', response.text)

elif response.status_code == 500:
raise InternalServerError('Internal server error', response.text)

else:
raise DynamicsError('Error: {0}'.format(response.status_code), response.text)

def _bulk_post_request(self, data, isolation: str):
"""Create a HTTP batch request.
Parameters:
data (dict): HTTP POST body data for the wanted API.
isolation: The isolation level for the batch request.
Returns:
A response from the request (dict).
"""

api_headers = {
'Authorization': self.__access_token,
'Accept': 'application/json',
'Isolation': isolation
}

response = requests.post(
'{0}'.format(self.__batch_url),
headers=api_headers,
json=data
)

if response.status_code == 200 or response.status_code == 201:
result = json.loads(response.text)
return result

elif response.status_code == 400:
raise WrongParamsError('Some of the parameters are wrong', response.text)

elif response.status_code == 401:
raise InvalidTokenError('Invalid token, try to refresh it', response.text)

elif response.status_code == 403:
raise NoPrivilegeError('Forbidden, the user has insufficient privilege', response.text)

elif response.status_code == 404:
raise NotFoundItemError('Not found item with ID', response.text)

elif response.status_code == 498:
raise ExpiredTokenError('Expired token, try to refresh it', response.text)

elif response.status_code == 500:
raise InternalServerError('Internal server error', response.text)

else:
raise DynamicsError('Error: {0}'.format(response.status_code), response.text)
44 changes: 44 additions & 0 deletions dynamics/apis/invoice_line_items.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from .api_base import ApiBase
from .invoices import PurchaseInvoices


class PurchaseInvoiceLineItems(ApiBase):
"""Class for PurchaseInvoiceLineItmes APIs."""

GET_PURCHASE_INVOICE_LINEITEMS = '/purchaseInvoices({0})/purchaseInvoiceLines'
POST_PURCHASE_INVOICE_LINEITEM = '/purchaseInvoices({0})/purchaseInvoiceLines'
BULK_POST_PURCHASE_INVOICE_LINEITEM = 'purchaseInvoices({0})/purchaseInvoiceLines'
DELETE_PURCHASE_INVOICE_LINEITEM = '/purchaseInvoiceLines({0})'


def get_all(self, purchase_invoice_id: str, **kwargs):
"""
Expand All @@ -26,3 +30,43 @@ def post(self, purchase_invoice_id: str, data):
return self._post_request(
data, PurchaseInvoiceLineItems.POST_PURCHASE_INVOICE_LINEITEM.format(purchase_invoice_id)
)

def delete(self, purchase_invoice_lineitem_id: str, **kwargs):
"""
Delete PurchaseInvoiceLineItem
:param purchase_invoice_lineitem_id:
:param kwargs:
:return:
"""
return self._delete_request({**kwargs}, PurchaseInvoiceLineItems.DELETE_PURCHASE_INVOICE_LINEITEM.format(purchase_invoice_lineitem_id))

def bulk_post(self, purchase_invoice_id: str, line_items: list, isolation: str = 'snapshot'):
"""
Create PurchaseInvoice LineItems in bulk.
:param purchase_invoice_id: The ID of the purchase invoice.
:param line_items: A list of line items to be added to the purchase invoice.
:param isolation: The isolation level of the bulk post request.
:return: Bulk response containing the results of the bulk post operation.
"""
# Prepare payload for bulk post
bulk_payload = []
for line_item in line_items:
# Prepare payload for each line item
line_item_payload = {
"method": "POST",
"url": PurchaseInvoiceLineItems.BULK_POST_PURCHASE_INVOICE_LINEITEM.format(purchase_invoice_id),
"headers": {
"CompanyId": self.company_id,
"Content-Type": "application/json",
"If-Match": "*"
},
"body": line_item
}
bulk_payload.append(line_item_payload)

# Create a bulk payload containing all line item requests
bulk_request_payload = {'requests': bulk_payload}

# Make the bulk post request
return self._bulk_post_request(bulk_request_payload, isolation)
10 changes: 10 additions & 0 deletions dynamics/apis/invoices.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class PurchaseInvoices(ApiBase):

GET_PURCHASE_INVOICES = '/purchaseInvoices'
POST_PURCHASE_INVOICE = '/purchaseInvoices'
DELETE_PURCHASE_INVOICE = '/purchaseInvoices({0})'

def get_all(self, **kwargs):
"""
Expand All @@ -25,3 +26,12 @@ def post(self, data):
:return:
"""
return self._post_request(data, PurchaseInvoices.POST_PURCHASE_INVOICE)

def delete(self, purchase_invoice_id: str, **kwargs):
"""
Delete PurchaseInvoice
:param purchase_invoice_id:
:param kwargs:
:return:
"""
return self._delete_request({**kwargs}, PurchaseInvoices.DELETE_PURCHASE_INVOICE.format(purchase_invoice_id))
10 changes: 10 additions & 0 deletions dynamics/apis/journal.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Journals(ApiBase):

GET_JOURNALS = '/journals'
POST_JOURNALS = '/journals'
DELETE_JOURNALS = '/journals({0})'

def get_all(self, **kwargs):
"""
Expand All @@ -25,3 +26,12 @@ def post(self, data):
:return:
"""
return self._post_request(data, Journals.POST_JOURNALS)

def delete(self, journal_id: str, **kwargs):
"""
Delete Journals
:param journal_id:
:param kwargs:
:return:
"""
return self._delete_request({**kwargs}, Journals.DELETE_JOURNALS.format(journal_id))
44 changes: 43 additions & 1 deletion dynamics/apis/journal_line_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@


class JournalLineItem(ApiBase):
"""Class for PurchaseInvoice APIs."""
"""Class for Journal LineItem APIs."""

GET_JOURNAL_LINE_ITEMS = '/journals({0})/journalLines'
POST_JOURNAL_LINE_ITEMS = '/journals({0})/journalLines'
BULK_POST_JOURNAL_LINEITEM = 'journals({0})/journalLines'
DELETE_JOURNAL_LINE_ITEMS = '/journalLines({0})'

def get_all(self, jounal_id, **kwargs):
"""
Expand All @@ -25,3 +27,43 @@ def post(self, journal_id, data):
:return:
"""
return self._post_request(data, JournalLineItem.POST_JOURNAL_LINE_ITEMS.format(journal_id))

def delete(self, jounral_lineitem_id: str, **kwargs):
"""
Delete Journal Line Item
:param jounral_lineitem_id:
:param kwargs:
:return:
"""
return self._delete_request({**kwargs}, JournalLineItem.DELETE_JOURNAL_LINE_ITEMS.format(jounral_lineitem_id))

def bulk_post(self, journal_id: str, line_items: list, isolation: str = 'snapshot'):
"""
Create Journal LineItems in bulk.
:param journal_id: The ID of the journal.
:param line_items: A list of line items to be added to the journal line items.
:param isolation: The isolation level of the bulk post request.
:return: Bulk response containing the results of the bulk post operation.
"""
# Prepare payload for bulk post
bulk_payload = []
for line_item in line_items:
# Prepare payload for each line item
line_item_payload = {
"method": "POST",
"url": JournalLineItem.BULK_POST_JOURNAL_LINEITEM.format(journal_id),
"headers": {
"CompanyId": self.company_id,
"Content-Type": "application/json",
"If-Match": "*"
},
"body": line_item
}
bulk_payload.append(line_item_payload)

# Create a bulk payload containing all line item requests
bulk_request_payload = {'requests': bulk_payload}

# Make the bulk post request
return self._bulk_post_request(bulk_request_payload, isolation)
42 changes: 41 additions & 1 deletion dynamics/core/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(
client_secret: str,
environment: str,
refresh_token: str,
company_id: str = None
company_id: str = None
):
"""
Constructor to initialize the Dynamics SDK.
Expand Down Expand Up @@ -48,6 +48,8 @@ def __init__(
# Get and set the access token
access_token = self.__refresh_access_token()
self.set_server_url()
self.set_batch_url()
self.set_company_id()
self.update_access_token(access_token)

def update_access_token(self, access_token: str):
Expand All @@ -74,6 +76,44 @@ def update_access_token(self, access_token: str):
for api in api_instances:
api.change_access_token(token)

def set_company_id(self):
"""
Set the Company ID in all API objects.
"""
api_instances = [
self.purchase_invoice_line_items,
self.journal_line_items
]

# Set company ID for all API instances
for api in api_instances:
api.set_company_id(self.__company_id)

def set_batch_url(self):
"""
Set the Batch URL in all API objects.
"""
batch_url = self.BASE_URL.format(environment=self.__environment)

batch_url = '{0}{1}'.format(batch_url, '/$batch')

api_instances = [
self.companies,
self.vendors,
self.accounts,
self.purchase_invoices,
self.journals,
self.journal_line_items,
self.purchase_invoice_line_items,
self.attachments,
self.employees,
self.locations
]

# Set batch URL for all API instances
for api in api_instances:
api.set_batch_url(batch_url)

def set_server_url(self):
"""
Set the Base URL in all API objects.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name='ms-dynamics-business-central-sdk',
version='1.2.0',
version='1.3.0',
author='Shwetabh Kumar',
author_email='[email protected]',
description='Python SDK for accessing Dynamics APIs',
Expand Down

0 comments on commit 5aca376

Please sign in to comment.