Skip to content

Commit

Permalink
Attach receipt to expense and add the details in the table (#123)
Browse files Browse the repository at this point in the history
  • Loading branch information
NileshPant1999 authored Jan 3, 2024
1 parent d7af238 commit b31c04a
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 32 deletions.
5 changes: 3 additions & 2 deletions apps/orgs/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ def create_fyle_connection(org_id: str):

fyle_credentials = FyleCredential.objects.get(org_id=org_id)

base_url = settings.FYLE_BASE_URL
client_id = settings.FYLE_CLIENT_ID
client_secret = settings.FYLE_CLIENT_SECRET
refresh_token = fyle_credentials.refresh_token
token_url = settings.FYLE_TOKEN_URI

server_url = '{}/platform/v1beta'.format(fyle_credentials.org.cluster_domain)

connection = Platform(
server_url=base_url,
server_url=server_url,
token_url=token_url,
client_id=client_id,
client_secret=client_secret,
Expand Down
75 changes: 52 additions & 23 deletions apps/travelperk/actions.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import requests
from datetime import datetime, timezone
import logging
from django.conf import settings
from fyle.platform import Platform

from workato import Workato

from apps.travelperk.models import Invoice, InvoiceLineItem, TravelPerk
from apps.orgs.models import Org, FyleCredential
from apps.travelperk.models import Invoice, InvoiceLineItem, TravelPerk, ImportedExpenseDetail
from apps.orgs.models import Org
from apps.orgs.utils import create_fyle_connection
from apps.orgs.exceptions import handle_workato_exception
from apps.names import TRAVELPERK

Expand Down Expand Up @@ -48,56 +49,78 @@ def download_file(remote_url, local_filename):
for chunk in response.iter_content(chunk_size=128):
file.write(chunk)
# Print a success message if the file is downloaded successfully
logger.log(f'Successfully downloaded the file to {local_filename}')
logger.info(f'Successfully downloaded the file to {local_filename}')
else:
# Print an error message if the file download fails
logger.log(f'Failed to download the file. Status code: {response.status_code}')
logger.info(f'Failed to download the file. Status code: {response.status_code}')


def upload_to_s3_presigned_url(file_path, presigned_url):
# Open the local file in binary read mode
with open(file_path, 'rb') as file:
headers = {
'Content-Type': 'application/pdf'
}

# Send a PUT request to the S3 pre-signed URL with the file data
response = requests.put(presigned_url, data=file)
response = requests.put(presigned_url, data=file, headers=headers)

# Check if the response status code is 200 (OK)
if response.status_code == 200:
# Print a success message if the file is uploaded successfully
logger.log(f'Successfully uploaded {file_path} to S3.')
logger.info(f'Successfully uploaded {file_path} to S3.')
else:
# Print an error message if the file upload fails
logger.log(f'Failed to upload {file_path} to S3. Status code: {response.status_code}')
logger.info(f'Failed to upload {file_path} to S3. Status code: {response.status_code}')


def create_expense_in_fyle(org_id: str, invoice: Invoice, invoice_lineitem: InvoiceLineItem):
def attach_reciept_to_expense(expense_id: str, invoice: Invoice, imported_expense: ImportedExpenseDetail, platform_connection: Platform):
"""
Function to attach receipt to expense
"""

file_payload = {
'data': {
'name': 'invoice.pdf',
"type": "RECEIPT"
}
}

file = platform_connection.v1beta.spender.files.create_file(file_payload)
generate_url = platform_connection.v1beta.spender.files.generate_file_urls({'data': {'id': file['data']['id']}})
download_path = 'tmp/{}-invoice.pdf'.format(expense_id)

download_file(invoice.pdf, download_path)
upload_to_s3_presigned_url(download_path, generate_url['data']['upload_url'])

attached_reciept = platform_connection.v1beta.spender.expenses.attach_receipt({'data': {'id': expense_id, 'file_id': file['data']['id']}})

if attached_reciept:
imported_expense.file_id = file['data']['id']
imported_expense.is_reciept_attached = True
imported_expense.save()


def create_expense_in_fyle(org_id: str, invoice: Invoice, invoice_lineitems: InvoiceLineItem):
"""
Create expense in Fyle
"""
org = Org.objects.get(id=org_id)
fyle_credentials = FyleCredential.objects.get(org=org)

for expense in invoice_lineitem:
for expense in invoice_lineitems:
payload = {
'data': {
'currency': invoice.currency,
'purpose': expense.description,
'merchant': expense.vendor,
'merchant': expense.vendor['name'] if expense.vendor else '',
'claim_amount': expense.total_amount,
'spent_at': '2023-06-01',
'spent_at': str(datetime.strptime(expense.expense_date, "%Y-%m-%d").replace(tzinfo=timezone.utc)),
'source': 'CORPORATE_CARD',
}
}

category_name = CATEGORY_MAP[expense.service]

server_url = '{}/platform/v1beta'.format(org.cluster_domain)
platform_connection = Platform(
server_url=server_url,
token_url=settings.FYLE_TOKEN_URI,
client_id=settings.FYLE_CLIENT_ID,
client_secret=settings.FYLE_CLIENT_SECRET,
refresh_token=fyle_credentials.refresh_token
)
platform_connection = create_fyle_connection(org.id)

query_params = {
'limit': 1,
Expand All @@ -108,11 +131,17 @@ def create_expense_in_fyle(org_id: str, invoice: Invoice, invoice_lineitem: Invo
}

category = platform_connection.v1beta.admin.categories.list(query_params=query_params)

if category['count'] > 0:
payload['data']['category_id'] = category['data'][0]['id']

expense = platform_connection.v1beta.spender.expenses.post(payload)
if expense:
imported_expense, _ = ImportedExpenseDetail.objects.update_or_create(
expense_id=expense['data']['id'],
org_id=org_id
)

attach_reciept_to_expense(expense['data']['id'], invoice, imported_expense, platform_connection)
invoice.exported_to_fyle = True
invoice.save()
11 changes: 10 additions & 1 deletion apps/travelperk/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __init__(self, credentials_object: TravelperkCredential, org_id: int):

client_id = settings.TRAVELPERK_CLIENT_ID
client_secret = settings.TRAVELPERK_CLIENT_SECRET
environment = settings.TRAVELPERK_ENVIRONMENT
environment = 'sandbox'
refresh_token = credentials_object.refresh_token

self.connection = Travelperk(client_id, client_secret, refresh_token, environment)
Expand Down Expand Up @@ -47,3 +47,12 @@ def create_webhook(self, data: dict):

return response

def delete_webhook_connection(self, webhook_subscription_id: str):
"""
Delete Webhook in Travelperk
:param webhook_subscription_id: Webhook Id
:return: Dict
"""

response = self.connection.webhooks.delete(webhook_subscription_id)
return response
35 changes: 35 additions & 0 deletions apps/travelperk/migrations/0008_auto_20240102_0517.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 3.1.14 on 2024-01-02 05:17

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('orgs', '0004_auto_20230627_1133'),
('travelperk', '0007_auto_20231219_0648'),
]

operations = [
migrations.AlterField(
model_name='invoice',
name='pdf',
field=models.TextField(help_text='URL to the PDF version of the invoice.'),
),
migrations.CreateModel(
name='ImportedExpenseDetail',
fields=[
('id', models.AutoField(help_text='Unique Id to indentify a Imported Expense Detail', primary_key=True, serialize=False)),
('expense_id', models.CharField(help_text='Expense Id', max_length=255)),
('file_id', models.CharField(help_text='File Id', max_length=255, null=True)),
('is_reciept_attached', models.BooleanField(default=False, help_text='If Reciept Is Attached')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Created at datetime')),
('updated_at', models.DateTimeField(auto_now=True, help_text='Updated at datetime')),
('org', models.ForeignKey(help_text='Reference to Org Table', on_delete=django.db.models.deletion.PROTECT, to='orgs.org')),
],
options={
'db_table': 'imported_expense_details',
},
),
]
12 changes: 8 additions & 4 deletions apps/travelperk/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Invoice(models.Model):
choices=[('reseller', 'Reseller'), ('direct', 'Direct')],
help_text='Mode of the invoice, indicating whether it is a reseller or direct invoice.'
)
pdf = models.URLField(help_text='URL to the PDF version of the invoice.')
pdf = models.TextField(help_text='URL to the PDF version of the invoice.')
profile_id = models.CharField(max_length=255, help_text='ID of the profile associated with the invoice.')
profile_name = models.CharField(max_length=255, help_text='Name of the profile associated with the invoice.')
reference = models.CharField(max_length=50, help_text='Reference information for the invoice (e.g., Trip #9876543).')
Expand All @@ -60,6 +60,7 @@ def create_or_update_invoices(invoice_data):
"""

# Create or update Invoice object based on serial_number
print('invoice date', invoice_data)
invoice_object, _ = Invoice.objects.update_or_create(
serial_number=invoice_data['serial_number'],
defaults={
Expand All @@ -82,7 +83,7 @@ def create_or_update_invoices(invoice_data):
'exported_to_fyle': False,
}
)

return invoice_object


Expand Down Expand Up @@ -205,7 +206,10 @@ class ImportedExpenseDetail(models.Model):
id = models.AutoField(primary_key=True, help_text='Unique Id to indentify a Imported Expense Detail')
org = models.ForeignKey(Org, on_delete=models.PROTECT, help_text='Reference to Org Table')
expense_id = models.CharField(max_length=255, help_text='Expense Id')
file_id = models.CharField(max_length=255, help_text='File Id')
is_reciept_attached = models.BooleanField(help_text='If Reciept Is Attached')
file_id = models.CharField(null=True, max_length=255, help_text='File Id')
is_reciept_attached = models.BooleanField(default=False, help_text='If Reciept Is Attached')
created_at = models.DateTimeField(auto_now_add=True, help_text='Created at datetime')
updated_at = models.DateTimeField(auto_now=True, help_text='Updated at datetime')

class Meta:
db_table = 'imported_expense_details'
4 changes: 3 additions & 1 deletion apps/travelperk/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from django.urls import path

from .views import TravelperkView, PostPackage, PostFolder, TravelperkConnection, \
TravekPerkConfigurationView, AwsS3Connection, RecipeStatusView, ConnectTravelperkView, TravelperkWebhookAPIView
TravekPerkConfigurationView, AwsS3Connection, RecipeStatusView, ConnectTravelperkView, \
TravelperkWebhookAPIView, DisconnectTravelperkView

app_name = 'travelperk'

Expand All @@ -14,5 +15,6 @@
path('travelperk_connection/', TravelperkConnection.as_view(), name='fyle-connection'),
path('s3_connection/', AwsS3Connection.as_view(), name='s3-connection'),
path('connect/', ConnectTravelperkView.as_view(), name='connect-travelperk'),
path('disconnect/', DisconnectTravelperkView.as_view(), name='disconnect-travelperk'),
path('travelperk_webhook/', TravelperkWebhookAPIView.as_view(), name='travelperk-webhook'),
]
32 changes: 31 additions & 1 deletion apps/travelperk/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,35 @@ def post(self, request, *args, **kwargs):
)


class DisconnectTravelperkView(generics.CreateAPIView):
"""
Api call to Disconnect Travelperk Connection
"""

def post(self, request, *args, **kwargs):
try:
travelperk = TravelPerk.objects.filter(org=kwargs['org_id']).first()
travelperk_creds = TravelperkCredential.objects.filter(org=kwargs['org_id']).first()

travelperk_connector = TravelperkConnector(travelperk_creds, kwargs['org_id'])
travelperk_connector.delete_webhook_connection(travelperk.webhook_subscription_id)

travelperk.webhook_subscription_id = None
travelperk.is_travelperk_connected = False
travelperk.save()

return Response(
data={'message': 'disconnected successfully'},
status=status.HTTP_200_OK
)

except TravelPerk.DoesNotExist:
return Response(
data={'message': 'no travelperk connection found'},
status=status.HTTP_404_NOT_FOUND
)


class ConnectTravelperkView(generics.CreateAPIView):
"""
Api Call to make Travelperk Connection in workato
Expand All @@ -235,7 +264,7 @@ def post(self, request, *args, **kwargs):

travelperk_webhook_data = {
'name': 'travelperk webhook invoice',
'url': 'https://webhook.site/c18f1a37-133d-4981-83d5-6d8234e26216',
'url': 'https://webhook.site/3446fc0e-cf2f-468b-bc54-8197c689ee97',
'secret': 'some secret',
'events': [
'invoice.issued'
Expand All @@ -247,6 +276,7 @@ def post(self, request, *args, **kwargs):
org=org,
defaults={
'webhook_id': created_webhook['id'],
'is_travelperk_connected': True
}
)

Expand Down

0 comments on commit b31c04a

Please sign in to comment.