From 6ca812397b043a70d7b64a52e8e1c1adc243f289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rafael=20R=20Nascimento?= Date: Mon, 2 Jan 2023 22:40:46 -0300 Subject: [PATCH 01/19] Implement base multprocess for APIs --- src/functions/bd_update_sales/api_ifood.py | 55 +++++++++++++++--- src/functions/bd_update_sales/api_rappi.py | 58 +++++++++++++++++++ src/functions/bd_update_sales/apis.py | 58 +++++++++++-------- src/functions/bd_update_sales/importer.py | 3 +- src/functions/bd_update_sales/main.py | 0 .../bd_update_sales/requirements.txt | 1 + src/functions/main.py | 10 ++-- src/functions/requirements.txt | 1 + 8 files changed, 149 insertions(+), 37 deletions(-) create mode 100644 src/functions/bd_update_sales/api_rappi.py delete mode 100644 src/functions/bd_update_sales/main.py diff --git a/src/functions/bd_update_sales/api_ifood.py b/src/functions/bd_update_sales/api_ifood.py index 32f1d177..3fd1fd81 100644 --- a/src/functions/bd_update_sales/api_ifood.py +++ b/src/functions/bd_update_sales/api_ifood.py @@ -1,16 +1,57 @@ -from importer import * from dotenv import load_dotenv +from .importer import ImporterApi_Interface +from retry import retry +import os, time def configure(): load_dotenv() - -class APIIfood(ImporterApi): - ifood = ImporterApi('ifood') - api_name = 'ifood' + +# STATIC VARIABLES +BASE_URL = 'https://merchant-api.ifood.com.br' +CLIENT_ID = os.getenv('IFOOD_CLIENT_ID') +CLIENT_SECRET = os.getenv('IFOOD_CLIENT_SECRET') + + +# Implements the "Importer" interface +# to provide all the necessary methods +# for downloading Ifood API data and saving it +# in the database. + +class ApiIfood(ImporterApi_Interface): - def __init__(self, api): + def __init__(self): configure() + self.api_name = 'ifood' + def connect(self): - print('Getting API KEY') \ No newline at end of file + print('Getting API KEY from {self.api_name}...') + + def download(self) -> bool: + print(f'Downloading data from {self.api_name}...') + + + def save_db(self) -> bool: + print(f'Saving data from {self.api_name}...') + + + @retry(delay=120, tries=1000) + def start(self): + + try: + + while True: + self.connect() + self.download() + self.save_db() + time.sleep(5) + + except Exception as e: + print(f'Error: {e}') + + + +def start(): + ifood = ApiIfood() + ifood.start() \ No newline at end of file diff --git a/src/functions/bd_update_sales/api_rappi.py b/src/functions/bd_update_sales/api_rappi.py new file mode 100644 index 00000000..b2268c2c --- /dev/null +++ b/src/functions/bd_update_sales/api_rappi.py @@ -0,0 +1,58 @@ +from dotenv import load_dotenv +from .importer import ImporterApi_Interface +from retry import retry +import os, time + +def configure(): + load_dotenv() + + +# STATIC VARIABLES +BASE_URL = 'https://merchant-api.ifood.com.br' +CLIENT_ID = os.getenv('IFOOD_CLIENT_ID') +CLIENT_SECRET = os.getenv('IFOOD_CLIENT_SECRET') + + +# Implements the "Importer" interface +# to provide all the necessary methods +# for downloading Rappi API data and saving it +# in the database. + +class ApiRappi(ImporterApi_Interface): + + def __init__(self): + configure() + self.api_name = 'rappi' + + + def connect(self): + print('Getting API KEY from {self.api_name}...') + + + def download(self) -> bool: + print(f'Downloading data from {self.api_name}...') + + + def save_db(self) -> bool: + print(f'Saving data from {self.api_name}...') + + + @retry(delay=120, tries=1000) + def start(self): + + try: + + while True: + self.connect() + self.download() + self.save_db() + time.sleep(7) + + except Exception as e: + print(f'Error: {e}') + + + +def start(): + rappi = ApiRappi() + rappi.start() \ No newline at end of file diff --git a/src/functions/bd_update_sales/apis.py b/src/functions/bd_update_sales/apis.py index 163c705e..a9b6a453 100644 --- a/src/functions/bd_update_sales/apis.py +++ b/src/functions/bd_update_sales/apis.py @@ -1,30 +1,40 @@ -class connectAPI: +from multiprocessing import Pool, Process +import importlib, multiprocessing + +def get_apis_list(): + + API_BASE_LIST = ['ifood', 'rappi'] + modules_name = lambda x : f'api_{x}' + MODULES_API_LIST = list(map(modules_name, API_BASE_LIST)) + return MODULES_API_LIST + - def __init__(self, api_name): - print(f'Starting API to {api_name}') - self.api_name = api_name - - - def send_email(self): - print(f'Sending email to {self.api_name}') - pass +def send_email(): + pass - def connect(self): - print(f'Connecting to {self.api_name}') - pass - +def run(): - def run(self): + modules = get_apis_list() + procs = [] + + for module in modules: + + print(f'Importing module: {module}') + mod = importlib.import_module(f'bd_update_sales.{module}') - try: - print(f'Running {self.api_name}') - self.send_email() - self.connect() - print(f'Finished sucessfully {self.api_name}!') - return True - except: - print(f'Error running {self.api_name}') - return False + # Creating process + proc = multiprocessing.Process(target=mod.start) + procs.append(proc) + - \ No newline at end of file + # Starting all processes + for proc in procs: + proc.start() + + print(f'All modules started...: {modules}') + + for proc in procs: + proc.join() + + \ No newline at end of file diff --git a/src/functions/bd_update_sales/importer.py b/src/functions/bd_update_sales/importer.py index ff69f838..b5f9d301 100644 --- a/src/functions/bd_update_sales/importer.py +++ b/src/functions/bd_update_sales/importer.py @@ -1,12 +1,13 @@ from abc import abstractmethod, ABC - +# Interface that should be used for all APIs. class ImporterApi_Interface(ABC): @abstractmethod def __init__(self, api): self.api = api print(f'Starting API to {self.api}...') + pass @abstractmethod diff --git a/src/functions/bd_update_sales/main.py b/src/functions/bd_update_sales/main.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/functions/bd_update_sales/requirements.txt b/src/functions/bd_update_sales/requirements.txt index e69de29b..076870c0 100644 --- a/src/functions/bd_update_sales/requirements.txt +++ b/src/functions/bd_update_sales/requirements.txt @@ -0,0 +1 @@ +retry \ No newline at end of file diff --git a/src/functions/main.py b/src/functions/main.py index 9554f7d2..43e103f3 100644 --- a/src/functions/main.py +++ b/src/functions/main.py @@ -1,10 +1,10 @@ from multiprocessing import Pool from requirements_generator import install_requirements -from .bd_update_sales import * +from bd_update_sales.apis import run -install_requirements() - -#with Pool() as p: -# results = p.imap_unordered(bd_update_sales) \ No newline at end of file +if __name__ == '__main__': + print(f'Starting system at {__name__}...') + install_requirements() + run() diff --git a/src/functions/requirements.txt b/src/functions/requirements.txt index e217fb96..15e72222 100644 --- a/src/functions/requirements.txt +++ b/src/functions/requirements.txt @@ -6,4 +6,5 @@ google-api-python-client google-auth-httplib2 oauth2client xlrd +retry flask From 76b56ff1cdde2399d46cbdfb65635969b25b6de5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rafael=20R=20Nascimento?= Date: Mon, 2 Jan 2023 22:59:38 -0300 Subject: [PATCH 02/19] Fix retry Ifood after Exception --- src/functions/bd_update_sales/api_ifood.py | 26 +++++++++++++++++----- src/functions/bd_update_sales/importer.py | 7 +----- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/functions/bd_update_sales/api_ifood.py b/src/functions/bd_update_sales/api_ifood.py index 3fd1fd81..a4e34384 100644 --- a/src/functions/bd_update_sales/api_ifood.py +++ b/src/functions/bd_update_sales/api_ifood.py @@ -1,7 +1,7 @@ from dotenv import load_dotenv from .importer import ImporterApi_Interface from retry import retry -import os, time +import os, time, requests def configure(): load_dotenv() @@ -9,8 +9,6 @@ def configure(): # STATIC VARIABLES BASE_URL = 'https://merchant-api.ifood.com.br' -CLIENT_ID = os.getenv('IFOOD_CLIENT_ID') -CLIENT_SECRET = os.getenv('IFOOD_CLIENT_SECRET') # Implements the "Importer" interface @@ -20,13 +18,30 @@ def configure(): class ApiIfood(ImporterApi_Interface): + accessToken = None + def __init__(self): configure() + self.CLIENT_ID = os.getenv('IFOOD_CLIENT_ID') + self.CLIENT_SECRET = os.getenv('IFOOD_CLIENT_SECRET') self.api_name = 'ifood' def connect(self): - print('Getting API KEY from {self.api_name}...') + print('Getting Access Token from {self.api_name}...') + + URL = f'{BASE_URL}/authentication/v1.0/ouath/token' + + post = requests.post(URL, data={ + 'clientId': self.CLIENT_ID, + 'clientSecret': self.CLIENT_SECRET, + 'grant_type': 'client_credentials' + }) + + print(post) + accessToken = post.json()['accessToken'] + print(f'Access Token: {accessToken}') + def download(self) -> bool: print(f'Downloading data from {self.api_name}...') @@ -36,7 +51,7 @@ def save_db(self) -> bool: print(f'Saving data from {self.api_name}...') - @retry(delay=120, tries=1000) + @retry(delay=10, tries=1000) def start(self): try: @@ -49,6 +64,7 @@ def start(self): except Exception as e: print(f'Error: {e}') + raise e diff --git a/src/functions/bd_update_sales/importer.py b/src/functions/bd_update_sales/importer.py index b5f9d301..caf12e90 100644 --- a/src/functions/bd_update_sales/importer.py +++ b/src/functions/bd_update_sales/importer.py @@ -4,27 +4,22 @@ class ImporterApi_Interface(ABC): @abstractmethod - def __init__(self, api): - self.api = api - print(f'Starting API to {self.api}...') + def __init__(self): pass @abstractmethod def connect(self) -> bool: - print(f'Connecting to {self.api}...') pass @abstractmethod def download(self) -> bool: - print(f'Downloading data from {self.api}...') pass @abstractmethod def save_db(self) -> bool: - print(f'Saving data from {self.api}...') pass From 1bc5aa9943db05e8710d22975d4bd7587ecc2ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rafael=20R=20Nascimento?= Date: Mon, 2 Jan 2023 23:39:29 -0300 Subject: [PATCH 03/19] Download merchants and orders --- src/functions/bd_update_sales/api_ifood.py | 59 +++++++++++++++++----- src/functions/bd_update_sales/api_rappi.py | 2 +- src/functions/bd_update_sales/apis.py | 2 +- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/functions/bd_update_sales/api_ifood.py b/src/functions/bd_update_sales/api_ifood.py index a4e34384..3aba9de7 100644 --- a/src/functions/bd_update_sales/api_ifood.py +++ b/src/functions/bd_update_sales/api_ifood.py @@ -18,8 +18,6 @@ def configure(): class ApiIfood(ImporterApi_Interface): - accessToken = None - def __init__(self): configure() self.CLIENT_ID = os.getenv('IFOOD_CLIENT_ID') @@ -28,36 +26,73 @@ def __init__(self): def connect(self): - print('Getting Access Token from {self.api_name}...') - - URL = f'{BASE_URL}/authentication/v1.0/ouath/token' + print(f'Getting Access Token from {self.api_name}...') - post = requests.post(URL, data={ + URL = f'{BASE_URL}/authentication/v1.0/oauth/token' + data={ 'clientId': self.CLIENT_ID, 'clientSecret': self.CLIENT_SECRET, - 'grant_type': 'client_credentials' - }) + 'grantType': 'client_credentials' + } - print(post) - accessToken = post.json()['accessToken'] - print(f'Access Token: {accessToken}') + post = requests.post(URL, data=data) + + self.accessToken = post.json()['accessToken'] + def download(self) -> bool: print(f'Downloading data from {self.api_name}...') + + # MERCHANTS + self.merchants = self.download_merchants() + + # ORDERS + self.orders = self.download_orders() + + + def download_merchants(self): + + merchants = [] + URL = f'{BASE_URL}/merchant/v1.0/merchants' + auth = f'Bearer {self.accessToken}' + headers = {"Authorization": auth} + + post = requests.get(URL, headers=headers) + + return post.json() + + + def download_orders(self): + + orders = [] + URL = f'{BASE_URL}/order/v1.0/events:polling' + auth = f'Bearer {self.accessToken}' + headers = {"Authorization": auth} + + post = requests.get(URL, headers=headers) + + if post.status_code != 200: + print(f'Status code: {post.status_code}') + return None + + return post.json() + def save_db(self) -> bool: print(f'Saving data from {self.api_name}...') + @retry(delay=10, tries=1000) def start(self): try: + self.connect() + while True: - self.connect() self.download() self.save_db() time.sleep(5) diff --git a/src/functions/bd_update_sales/api_rappi.py b/src/functions/bd_update_sales/api_rappi.py index b2268c2c..2ae6719e 100644 --- a/src/functions/bd_update_sales/api_rappi.py +++ b/src/functions/bd_update_sales/api_rappi.py @@ -26,7 +26,7 @@ def __init__(self): def connect(self): - print('Getting API KEY from {self.api_name}...') + print(f'Getting API KEY from {self.api_name}...') def download(self) -> bool: diff --git a/src/functions/bd_update_sales/apis.py b/src/functions/bd_update_sales/apis.py index a9b6a453..a77af8f6 100644 --- a/src/functions/bd_update_sales/apis.py +++ b/src/functions/bd_update_sales/apis.py @@ -3,7 +3,7 @@ def get_apis_list(): - API_BASE_LIST = ['ifood', 'rappi'] + API_BASE_LIST = ['ifood'] modules_name = lambda x : f'api_{x}' MODULES_API_LIST = list(map(modules_name, API_BASE_LIST)) return MODULES_API_LIST From c57971aae9f10a09b33a90709947eb9983256ce1 Mon Sep 17 00:00:00 2001 From: jrrn Date: Wed, 4 Jan 2023 19:36:29 -0300 Subject: [PATCH 04/19] Improve separation of responsibility --- src/functions/bd_update_sales/api_ifood.py | 2 +- src/functions/bd_update_sales/apis.py | 45 ++++++++++++++----- .../bd_update_sales/requirements.txt | 3 +- src/functions/requirements.txt | 7 +-- src/functions/requirements_generator.py | 2 +- 5 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/functions/bd_update_sales/api_ifood.py b/src/functions/bd_update_sales/api_ifood.py index 3aba9de7..e63d891f 100644 --- a/src/functions/bd_update_sales/api_ifood.py +++ b/src/functions/bd_update_sales/api_ifood.py @@ -1,5 +1,5 @@ from dotenv import load_dotenv -from .importer import ImporterApi_Interface +from importer import ImporterApi_Interface from retry import retry import os, time, requests diff --git a/src/functions/bd_update_sales/apis.py b/src/functions/bd_update_sales/apis.py index a77af8f6..68fbfd0b 100644 --- a/src/functions/bd_update_sales/apis.py +++ b/src/functions/bd_update_sales/apis.py @@ -1,6 +1,7 @@ from multiprocessing import Pool, Process import importlib, multiprocessing + def get_apis_list(): API_BASE_LIST = ['ifood'] @@ -8,33 +9,55 @@ def get_apis_list(): MODULES_API_LIST = list(map(modules_name, API_BASE_LIST)) return MODULES_API_LIST - -def send_email(): - pass - -def run(): - - modules = get_apis_list() + +def transform_module_into_processes(modules): + procs = [] for module in modules: print(f'Importing module: {module}') - mod = importlib.import_module(f'bd_update_sales.{module}') + mod = importlib.import_module(module) # Creating process proc = multiprocessing.Process(target=mod.start) procs.append(proc) - + return procs + + + +def run_processes(procs): + # Starting all processes for proc in procs: proc.start() - print(f'All modules started...: {modules}') for proc in procs: proc.join() - \ No newline at end of file + + + +def send_email(): + pass + + + +def run(): + + # Gets the list of all modules(files) starting with "api_" + modules = get_apis_list() + + # Turns all modules into processes, with the "start" method to run + procs = transform_module_into_processes(modules) + + run_processes(procs) + + + + +if __name__ == '__main__': + run() \ No newline at end of file diff --git a/src/functions/bd_update_sales/requirements.txt b/src/functions/bd_update_sales/requirements.txt index 076870c0..aa2b9440 100644 --- a/src/functions/bd_update_sales/requirements.txt +++ b/src/functions/bd_update_sales/requirements.txt @@ -1 +1,2 @@ -retry \ No newline at end of file +retry +python-dotenv \ No newline at end of file diff --git a/src/functions/requirements.txt b/src/functions/requirements.txt index 15e72222..92b61091 100644 --- a/src/functions/requirements.txt +++ b/src/functions/requirements.txt @@ -1,10 +1,11 @@ -gspread -pandas requests +gspread openpyxl +pandas google-api-python-client google-auth-httplib2 oauth2client xlrd -retry flask +retry +python-dotenv diff --git a/src/functions/requirements_generator.py b/src/functions/requirements_generator.py index bdd8978c..5c8cb5d1 100644 --- a/src/functions/requirements_generator.py +++ b/src/functions/requirements_generator.py @@ -4,7 +4,7 @@ try: - ACTUAL_FOLDER = os.getcwd() + "\\src\\functions" + ACTUAL_FOLDER = os.path.join(os.getcwd(), "src", "functions") ACTUAL_FOLDERS_REQUIREMENTS = os.listdir("src/functions") ACTUAL_FILE_REQUIREMENTS = os.path.join(ACTUAL_FOLDER, "requirements.txt") os.path.exists(ACTUAL_FOLDER) From 26adb4bdb8f25c461918824207ef75630e6780d1 Mon Sep 17 00:00:00 2001 From: jrrn Date: Wed, 4 Jan 2023 21:28:08 -0300 Subject: [PATCH 05/19] Add connection to BD Sales --- docker-compose.yml | 3 +- src/functions/bd_update_sales/api_ifood.py | 24 ++++- src/functions/bd_update_sales/bd_sales.py | 101 ++++++++++++++++++ .../bd_update_sales/requirements.txt | 3 +- 4 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 src/functions/bd_update_sales/bd_sales.py diff --git a/docker-compose.yml b/docker-compose.yml index bc0ebc9e..e1e55cbd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,8 +19,7 @@ services: context: ./src/bot dockerfile: Dockerfile restart: always - environment: - - API_KEY=${API_KEY} + site: build: diff --git a/src/functions/bd_update_sales/api_ifood.py b/src/functions/bd_update_sales/api_ifood.py index e63d891f..17310248 100644 --- a/src/functions/bd_update_sales/api_ifood.py +++ b/src/functions/bd_update_sales/api_ifood.py @@ -1,7 +1,8 @@ from dotenv import load_dotenv from importer import ImporterApi_Interface from retry import retry -import os, time, requests +from bd_sales import DbSalesIfood +import os, time, requests, hashlib def configure(): load_dotenv() @@ -42,13 +43,17 @@ def connect(self): def download(self) -> bool: + print(f'Downloading data from {self.api_name}...') # MERCHANTS self.merchants = self.download_merchants() + self.merchants_hash_downloaded = hashlib.md5(str(self.merchants).encode('utf-8')).hexdigest() # ORDERS self.orders = self.download_orders() + + return True def download_merchants(self): @@ -80,9 +85,18 @@ def download_orders(self): - def save_db(self) -> bool: + def save_db(self): print(f'Saving data from {self.api_name}...') + db = DbSalesIfood() + + if self.merchants_hash_downloaded != self.merchants_hash_saved: + db.insert_merchants(self.merchants) + self.merchants_hash_saved = self.merchants_hash_downloaded + + db.insert_orders(self.orders) + + @retry(delay=10, tries=1000) @@ -91,10 +105,12 @@ def start(self): try: self.connect() + self.merchants_hash_saved = None while True: - self.download() - self.save_db() + if self.download(): + self.save_db() + time.sleep(5) except Exception as e: diff --git a/src/functions/bd_update_sales/bd_sales.py b/src/functions/bd_update_sales/bd_sales.py new file mode 100644 index 00000000..e9ce4e7d --- /dev/null +++ b/src/functions/bd_update_sales/bd_sales.py @@ -0,0 +1,101 @@ +from dotenv import load_dotenv +import os, psycopg2 + + +def configure(): + load_dotenv() + + +class DbSales(): + + + def __init__(self): + configure() + self.DB_HOST = os.getenv('DATABASE_SALES_ADDRESS') + self.DB_PORT = os.getenv('DATABASE_SALES_PORT') + self.DB_USER = os.getenv('DATABASE_SALES_USER') + self.DB_PASSWORD = os.getenv('DATABASE_SALES_PASSWORD') + self.DB_NAME = 'db' + + + + def connect(self): + + try: + conecction_format = f'dbname={self.DB_NAME} user={self.DB_USER} password={self.DB_PASSWORD} host={self.DB_HOST} port={self.DB_PORT}' + self.connection = psycopg2.connect(conecction_format) + print('Connected to database!') + self.db = self.connection.cursor() + return True + + except Exception as e: + print(f"Error while connecting: {e}") + return False + + + + def execute(self, command, values): + + try: + + self.db.execute(command, values) + + try: + self.bd_return = self.db.fetchall() + except: + self.bd_return = None + + print(f'Executed: {command} with values: {values} -> Result: {self.bd_return}') + return self.bd_return + + except Exception as e: + print(f"Error while executing: {e}") + return None + + + + def commit(self): + + try: + self.db.commit() + return True + + except Exception as e: + print(f"Error while commiting: {e}") + return False + + + + def disconnect(self): + self.db.close() + self.connection.close() + print('Disconnected from database!') + + + +class DbSalesIfood(): + + def insert_merchants(self, merchants): + + db = DbSales() + db.connect() + + for merchant in merchants: + + print(f'Inserting merchant: {merchant}') + + db.disconnect() + + + + + def insert_orders(self, orders): + + db = DbSales() + db.connect() + + for order in orders: + + print(f'Inserting order: {order}') + + db.disconnect() \ No newline at end of file diff --git a/src/functions/bd_update_sales/requirements.txt b/src/functions/bd_update_sales/requirements.txt index aa2b9440..ca6d0bad 100644 --- a/src/functions/bd_update_sales/requirements.txt +++ b/src/functions/bd_update_sales/requirements.txt @@ -1,2 +1,3 @@ retry -python-dotenv \ No newline at end of file +python-dotenv +psychopg2-binary \ No newline at end of file From 5aa268459e73f387c27731fb4cdd47766261d3c6 Mon Sep 17 00:00:00 2001 From: jrrn Date: Wed, 4 Jan 2023 22:06:06 -0300 Subject: [PATCH 06/19] Fix Ifood API Flow --- src/functions/bd_update_sales/api_ifood.py | 75 ++++++++++++++++------ 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/src/functions/bd_update_sales/api_ifood.py b/src/functions/bd_update_sales/api_ifood.py index 17310248..2ba42230 100644 --- a/src/functions/bd_update_sales/api_ifood.py +++ b/src/functions/bd_update_sales/api_ifood.py @@ -24,10 +24,18 @@ def __init__(self): self.CLIENT_ID = os.getenv('IFOOD_CLIENT_ID') self.CLIENT_SECRET = os.getenv('IFOOD_CLIENT_SECRET') self.api_name = 'ifood' + self.accessToken = None + ############################### + # CONNECT # + ############################### + def connect(self): - print(f'Getting Access Token from {self.api_name}...') + print(f'Getting Access Token from {self.api_name}...') + + if self.accessToken != None: + return URL = f'{BASE_URL}/authentication/v1.0/oauth/token' data={ @@ -39,16 +47,28 @@ def connect(self): post = requests.post(URL, data=data) self.accessToken = post.json()['accessToken'] + self.configure_headers(self.accessToken) + print(f'\tAccess Token obtained!') - + + + def configure_headers(self, access_token): + auth = f'Bearer {self.accessToken}' + self.headers = {"Authorization": auth} + + + ############################### + # DOWNLOAD # + ############################### def download(self) -> bool: print(f'Downloading data from {self.api_name}...') # MERCHANTS - self.merchants = self.download_merchants() - self.merchants_hash_downloaded = hashlib.md5(str(self.merchants).encode('utf-8')).hexdigest() + merchants = self.download_merchants() + self.merchants_details = self.download_merchants_details(merchants) + self.merchants_hash_downloaded = hashlib.md5(str(merchants).encode('utf-8')).hexdigest() # ORDERS self.orders = self.download_orders() @@ -59,23 +79,29 @@ def download(self) -> bool: def download_merchants(self): merchants = [] - URL = f'{BASE_URL}/merchant/v1.0/merchants' - auth = f'Bearer {self.accessToken}' - headers = {"Authorization": auth} - - post = requests.get(URL, headers=headers) + URL = f'{BASE_URL}/merchant/v1.0/merchants' + post = requests.get(URL, headers=self.headers) return post.json() + + + def download_merchants_details(self, merchants): + + URL = f'{BASE_URL}/merchant/v1.0/merchants/' + merchants_details = [] + + for merchant in merchants: + post = requests.get(URL + merchant['id'], headers=self.headers) + merchants_details.append(post.json()) + + return merchants_details def download_orders(self): orders = [] - URL = f'{BASE_URL}/order/v1.0/events:polling' - auth = f'Bearer {self.accessToken}' - headers = {"Authorization": auth} - - post = requests.get(URL, headers=headers) + URL = f'{BASE_URL}/order/v1.0/events:polling' + post = requests.get(URL, headers=self.headers) if post.status_code != 200: print(f'Status code: {post.status_code}') @@ -84,6 +110,9 @@ def download_orders(self): return post.json() + ############################### + # SAVE # + ############################### def save_db(self): print(f'Saving data from {self.api_name}...') @@ -91,14 +120,24 @@ def save_db(self): db = DbSalesIfood() if self.merchants_hash_downloaded != self.merchants_hash_saved: - db.insert_merchants(self.merchants) + db.insert_merchants(self.merchants_details) self.merchants_hash_saved = self.merchants_hash_downloaded - - db.insert_orders(self.orders) - + if self.orders != None: + db.insert_orders(self.orders) + def send_acks(): + pass + + + def send_ack(id): + pass + + ############################### + # WATCHER # + ############################### + @retry(delay=10, tries=1000) def start(self): From 42d609dd895da882f26f51d98323399e8e1870da Mon Sep 17 00:00:00 2001 From: jrrn Date: Thu, 5 Jan 2023 13:10:29 -0300 Subject: [PATCH 07/19] Fix Django works in Docker --- docker-compose.yml | 5 +++-- src/site/Dockerfile | 9 ++++++--- src/site/erp/settings.py | 7 ++++--- src/site/manage.py | 3 +++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e1e55cbd..470f87bf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,11 +9,10 @@ services: POSTGRES_PASSWORD: password POSTGRES_USER: user POSTGRES_DB: db - ports: - - 5432:5432 volumes: - ./db_data:/var/lib/postgresql/data + bot: build: context: ./src/bot @@ -35,6 +34,7 @@ services: volumes: - ./src/site:/app + pg-admin: image: dpage/pgadmin4 restart: always @@ -48,6 +48,7 @@ services: links: - db:db + functions: build: context: ./src/functions diff --git a/src/site/Dockerfile b/src/site/Dockerfile index 314c62a6..4872f94d 100644 --- a/src/site/Dockerfile +++ b/src/site/Dockerfile @@ -1,10 +1,13 @@ FROM python:3.10 +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + COPY . /app WORKDIR /app -RUN pip install requests django Pillow psycopg2 coveralls +RUN pip install requests django Pillow psycopg2 coveralls python-dotenv -EXPOSE 8000 +RUN chown -R $USER:$USER manage.py -CMD ["python", "manage.py", "runserver"] \ No newline at end of file +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] \ No newline at end of file diff --git a/src/site/erp/settings.py b/src/site/erp/settings.py index 83ac7e18..f96bd18b 100644 --- a/src/site/erp/settings.py +++ b/src/site/erp/settings.py @@ -2,8 +2,9 @@ from dotenv import load_dotenv import os -def configure(): - load_dotenv() + +load_dotenv() + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -74,7 +75,7 @@ def configure(): "NAME": "db", "USER": "user", "PASSWORD": "password", - "HOST": "", + "HOST": "db", "PORT": "5432", } } diff --git a/src/site/manage.py b/src/site/manage.py index a1b944ce..05e9ac76 100644 --- a/src/site/manage.py +++ b/src/site/manage.py @@ -2,6 +2,7 @@ """Django's command-line utility for administrative tasks.""" import os import sys +import time def main(): @@ -19,4 +20,6 @@ def main(): if __name__ == "__main__": + print("Waiting for database to be ready...") + #time.sleep(15) main() From 32f12b445a34ccfa56b6b72539ce89f42ad35d74 Mon Sep 17 00:00:00 2001 From: jrrn Date: Sat, 7 Jan 2023 12:24:29 -0300 Subject: [PATCH 08/19] Start tests --- .vscode/settings.json | 6 ++--- src/functions/bd_update_sales/api_ifood.py | 2 ++ src/functions/bd_update_sales/apis.py | 12 +++++----- src/functions/test/test_bd_update_sales.py | 19 ++++++++++++++++ src/site/integrations/models.py | 2 ++ src/site/integrations/tests.py | 1 + src/site/manage.py | 7 ++++-- src/site/user/admin.py | 2 +- ...emove_userclient_user_document_and_more.py | 22 +++++++++++++++++++ src/site/user/models.py | 3 ++- 10 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 src/functions/test/test_bd_update_sales.py create mode 100644 src/site/user/migrations/0003_remove_userclient_user_document_and_more.py diff --git a/.vscode/settings.json b/.vscode/settings.json index e4c9f29a..a4cca64f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,10 +2,10 @@ "python.testing.unittestArgs": [ "-v", "-s", - "./src", + "./src/functions/test", "-p", - "*test*.py" + "test*.py" ], "python.testing.pytestEnabled": false, - "python.testing.unittestEnabled": true + "python.testing.unittestEnabled": true, } \ No newline at end of file diff --git a/src/functions/bd_update_sales/api_ifood.py b/src/functions/bd_update_sales/api_ifood.py index 2ba42230..bea3ad9e 100644 --- a/src/functions/bd_update_sales/api_ifood.py +++ b/src/functions/bd_update_sales/api_ifood.py @@ -115,6 +115,7 @@ def download_orders(self): ############################### def save_db(self): + print(f'Saving data from {self.api_name}...') db = DbSalesIfood() @@ -134,6 +135,7 @@ def send_acks(): def send_ack(id): pass + ############################### # WATCHER # ############################### diff --git a/src/functions/bd_update_sales/apis.py b/src/functions/bd_update_sales/apis.py index 68fbfd0b..854ab491 100644 --- a/src/functions/bd_update_sales/apis.py +++ b/src/functions/bd_update_sales/apis.py @@ -2,12 +2,11 @@ import importlib, multiprocessing -def get_apis_list(): +def get_apis_list(apis_name): - API_BASE_LIST = ['ifood'] modules_name = lambda x : f'api_{x}' - MODULES_API_LIST = list(map(modules_name, API_BASE_LIST)) - return MODULES_API_LIST + modules_api_list = list(map(modules_name, apis_name)) + return modules_api_list @@ -48,8 +47,9 @@ def send_email(): def run(): - # Gets the list of all modules(files) starting with "api_" - modules = get_apis_list() + # Gets the list of all modules(files) starting with "api_ + base list" + API_BASE_LIST = ['ifood'] + modules = get_apis_list(API_BASE_LIST) # Turns all modules into processes, with the "start" method to run procs = transform_module_into_processes(modules) diff --git a/src/functions/test/test_bd_update_sales.py b/src/functions/test/test_bd_update_sales.py new file mode 100644 index 00000000..cb2d80e8 --- /dev/null +++ b/src/functions/test/test_bd_update_sales.py @@ -0,0 +1,19 @@ +import unittest, sys, os + +# Configure path to import modules +current = os.path.dirname(os.path.realpath(__file__)) +parent = os.path.dirname(current) +sys.path.append(parent) + +from bd_update_sales.apis import * + + + +class TestApi(unittest.TestCase): + + def test_api_list(self): + self.assertEqual(get_apis_list(['ifood', 'rappi']), ['api_ifood', 'api_rappi']) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/src/site/integrations/models.py b/src/site/integrations/models.py index 4b623035..644ad9ef 100644 --- a/src/site/integrations/models.py +++ b/src/site/integrations/models.py @@ -3,8 +3,10 @@ # Create your models here. class Integration(models.Model): + client_document = models.ForeignKey(Client, on_delete=models.DO_NOTHING) integration_name = models.CharField(max_length=100) + integration_client_id = models.CharField(max_length=100) integration_api_key = models.CharField(max_length=100) def __str__(self): diff --git a/src/site/integrations/tests.py b/src/site/integrations/tests.py index 7ce503c2..4298ace5 100644 --- a/src/site/integrations/tests.py +++ b/src/site/integrations/tests.py @@ -1,3 +1,4 @@ from django.test import TestCase +from .models import Integration # Create your tests here. diff --git a/src/site/manage.py b/src/site/manage.py index 05e9ac76..42104cd2 100644 --- a/src/site/manage.py +++ b/src/site/manage.py @@ -20,6 +20,9 @@ def main(): if __name__ == "__main__": - print("Waiting for database to be ready...") - #time.sleep(15) + + if sys.argv[1] == "runserver": + print("Waiting for database to be ready...") + time.sleep(15) + main() diff --git a/src/site/user/admin.py b/src/site/user/admin.py index 93175cb2..8a5ff466 100644 --- a/src/site/user/admin.py +++ b/src/site/user/admin.py @@ -10,6 +10,6 @@ class ClientAdmin(admin.ModelAdmin): @admin.register(UserClient) class UserClientAdmin(admin.ModelAdmin): - list_display = ('username', 'user_document', 'whatsapp_number', 'telegram_username') + list_display = ('username', 'whatsapp_number', 'telegram_username') list_editable = ('whatsapp_number', 'telegram_username') search_fields = ('username', 'user_document', 'whatsapp_number', 'telegram_username') \ No newline at end of file diff --git a/src/site/user/migrations/0003_remove_userclient_user_document_and_more.py b/src/site/user/migrations/0003_remove_userclient_user_document_and_more.py new file mode 100644 index 00000000..261ef1e2 --- /dev/null +++ b/src/site/user/migrations/0003_remove_userclient_user_document_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.1.5 on 2023-01-06 22:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0002_alter_userclient_user_document'), + ] + + operations = [ + migrations.RemoveField( + model_name='userclient', + name='user_document', + ), + migrations.AddField( + model_name='userclient', + name='user_document', + field=models.ManyToManyField(to='user.client'), + ), + ] diff --git a/src/site/user/models.py b/src/site/user/models.py index be7ddd8e..ca2af831 100644 --- a/src/site/user/models.py +++ b/src/site/user/models.py @@ -2,6 +2,7 @@ from django.contrib.auth.models import AbstractUser class Client(models.Model): + document = models.CharField(max_length=14, primary_key=True) nome_fantasia = models.CharField(max_length=100) razao_social = models.CharField(max_length=100) @@ -13,7 +14,7 @@ def __str__(self): class UserClient(AbstractUser): - user_document = models.ForeignKey(Client, on_delete=models.DO_NOTHING, default='1') + user_document = models.ManyToManyField(Client) whatsapp_number = models.CharField(max_length=20, unique=True) whatsapp_chat_id = models.CharField(max_length=20, unique=True) From 5fc2562fc2f54ae0cd9b885dbf145d52082db137 Mon Sep 17 00:00:00 2001 From: jrrn Date: Sat, 7 Jan 2023 12:42:00 -0300 Subject: [PATCH 09/19] Add constraint to Integration Model to avoid duplicates APIS --- .vscode/settings.json | 2 +- src/functions/test/test_bd_update_sales.py | 6 +----- src/functions/test/test_extract.py | 4 ---- src/site/integrations/models.py | 2 ++ 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a4cca64f..02d68401 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,7 @@ "python.testing.unittestArgs": [ "-v", "-s", - "./src/functions/test", + "./src/functions/test/", "-p", "test*.py" ], diff --git a/src/functions/test/test_bd_update_sales.py b/src/functions/test/test_bd_update_sales.py index cb2d80e8..0de0b6ff 100644 --- a/src/functions/test/test_bd_update_sales.py +++ b/src/functions/test/test_bd_update_sales.py @@ -12,8 +12,4 @@ class TestApi(unittest.TestCase): def test_api_list(self): - self.assertEqual(get_apis_list(['ifood', 'rappi']), ['api_ifood', 'api_rappi']) - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file + self.assertEqual(get_apis_list(['ifood', 'rappi']), ['api_ifood', 'api_rappi']) \ No newline at end of file diff --git a/src/functions/test/test_extract.py b/src/functions/test/test_extract.py index adc5a38f..3062764a 100644 --- a/src/functions/test/test_extract.py +++ b/src/functions/test/test_extract.py @@ -21,7 +21,3 @@ def test_extract_type(self): self.assertEqual(extract_type('GETNET DEBITO ELO', 'XYZ'), 'GETNET') self.assertEqual(extract_type('CONVENIO', 'DEB AUTOMATICO'), 'DEB AUTOMATICO') self.assertEqual(extract_type('DEBITO CONVENIOS ID', '9876'), 'DEB AUTOMATICO') - - -if __name__ == '__main__': - unittest.main() diff --git a/src/site/integrations/models.py b/src/site/integrations/models.py index 644ad9ef..7a1cb2fb 100644 --- a/src/site/integrations/models.py +++ b/src/site/integrations/models.py @@ -8,6 +8,8 @@ class Integration(models.Model): integration_name = models.CharField(max_length=100) integration_client_id = models.CharField(max_length=100) integration_api_key = models.CharField(max_length=100) + + models.UniqueConstraint(fields=['client_document', 'integration_name'], name='unique_integration') def __str__(self): return self.integration_name \ No newline at end of file From 41cfb347c6129b2fec816b09cf79ce4cce924b1d Mon Sep 17 00:00:00 2001 From: jrrn Date: Sat, 7 Jan 2023 12:43:42 -0300 Subject: [PATCH 10/19] Add structure to API Transaction --- src/site/api_transaction/__init__.py | 0 src/site/api_transaction/admin.py | 3 +++ src/site/api_transaction/apps.py | 6 ++++++ src/site/api_transaction/migrations/__init__.py | 0 src/site/api_transaction/models.py | 3 +++ src/site/api_transaction/tests.py | 3 +++ src/site/api_transaction/views.py | 3 +++ 7 files changed, 18 insertions(+) create mode 100644 src/site/api_transaction/__init__.py create mode 100644 src/site/api_transaction/admin.py create mode 100644 src/site/api_transaction/apps.py create mode 100644 src/site/api_transaction/migrations/__init__.py create mode 100644 src/site/api_transaction/models.py create mode 100644 src/site/api_transaction/tests.py create mode 100644 src/site/api_transaction/views.py diff --git a/src/site/api_transaction/__init__.py b/src/site/api_transaction/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/site/api_transaction/admin.py b/src/site/api_transaction/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/src/site/api_transaction/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/src/site/api_transaction/apps.py b/src/site/api_transaction/apps.py new file mode 100644 index 00000000..7f359bab --- /dev/null +++ b/src/site/api_transaction/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiTransactionConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'api_transaction' diff --git a/src/site/api_transaction/migrations/__init__.py b/src/site/api_transaction/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/site/api_transaction/models.py b/src/site/api_transaction/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/src/site/api_transaction/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/src/site/api_transaction/tests.py b/src/site/api_transaction/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/src/site/api_transaction/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/site/api_transaction/views.py b/src/site/api_transaction/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/src/site/api_transaction/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 8dcf1ca379ef16d6dc8e9a6297949b94e72128cc Mon Sep 17 00:00:00 2001 From: jrrn Date: Mon, 9 Jan 2023 13:45:49 -0300 Subject: [PATCH 11/19] Downloading orders details from IFood --- docker-compose.yml | 2 ++ src/functions/bd_update_sales/api_ifood.py | 30 +++++++++++++++---- ...egration_integration_client_id_and_more.py | 23 ++++++++++++++ src/site/integrations/models.py | 4 +-- 4 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 src/site/integrations/migrations/0003_integration_integration_client_id_and_more.py diff --git a/docker-compose.yml b/docker-compose.yml index 470f87bf..1bbd7b87 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,8 @@ services: POSTGRES_DB: db volumes: - ./db_data:/var/lib/postgresql/data + ports: + - 5432:5432 bot: diff --git a/src/functions/bd_update_sales/api_ifood.py b/src/functions/bd_update_sales/api_ifood.py index bea3ad9e..d7dbd94f 100644 --- a/src/functions/bd_update_sales/api_ifood.py +++ b/src/functions/bd_update_sales/api_ifood.py @@ -66,18 +66,20 @@ def download(self) -> bool: print(f'Downloading data from {self.api_name}...') # MERCHANTS - merchants = self.download_merchants() - self.merchants_details = self.download_merchants_details(merchants) - self.merchants_hash_downloaded = hashlib.md5(str(merchants).encode('utf-8')).hexdigest() + self.merchants = self.download_merchants() + #self.merchants_details = self.download_merchants_details(merchants) + self.merchants_hash_downloaded = hashlib.md5(str(self.merchants).encode('utf-8')).hexdigest() # ORDERS self.orders = self.download_orders() + self.orders_details = self.download_orders_details(self.orders) return True def download_merchants(self): + print('\tDownloading merchants...') merchants = [] URL = f'{BASE_URL}/merchant/v1.0/merchants' post = requests.get(URL, headers=self.headers) @@ -87,6 +89,7 @@ def download_merchants(self): def download_merchants_details(self, merchants): + print('\tDownloading merchants details...') URL = f'{BASE_URL}/merchant/v1.0/merchants/' merchants_details = [] @@ -99,17 +102,34 @@ def download_merchants_details(self, merchants): def download_orders(self): + print('\tDownloading orders...') orders = [] URL = f'{BASE_URL}/order/v1.0/events:polling' post = requests.get(URL, headers=self.headers) if post.status_code != 200: - print(f'Status code: {post.status_code}') + print(f'\t\tStatus code: {post.status_code}') return None return post.json() + def download_orders_details(self, orders): + + print('\tDownloading orders details...') + orders_details = [] + URL = f'{BASE_URL}/order/v1.0/orders/' + + for order in orders: + URL = f"{URL}{order['orderId']}" + post = requests.get(URL, headers=self.headers) + + if post.status_code == 200: + orders_details.append(post.json()) + + return orders_details + + ############################### # SAVE # ############################### @@ -121,7 +141,7 @@ def save_db(self): db = DbSalesIfood() if self.merchants_hash_downloaded != self.merchants_hash_saved: - db.insert_merchants(self.merchants_details) + db.insert_merchants(self.merchants) self.merchants_hash_saved = self.merchants_hash_downloaded if self.orders != None: diff --git a/src/site/integrations/migrations/0003_integration_integration_client_id_and_more.py b/src/site/integrations/migrations/0003_integration_integration_client_id_and_more.py new file mode 100644 index 00000000..3d71abd4 --- /dev/null +++ b/src/site/integrations/migrations/0003_integration_integration_client_id_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.5 on 2023-01-07 15:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('integrations', '0002_alter_integration_client_document'), + ] + + operations = [ + migrations.AddField( + model_name='integration', + name='integration_client_id', + field=models.CharField(blank=True, max_length=100), + ), + migrations.AlterField( + model_name='integration', + name='integration_api_key', + field=models.CharField(blank=True, max_length=100), + ), + ] diff --git a/src/site/integrations/models.py b/src/site/integrations/models.py index 7a1cb2fb..980c54c5 100644 --- a/src/site/integrations/models.py +++ b/src/site/integrations/models.py @@ -6,8 +6,8 @@ class Integration(models.Model): client_document = models.ForeignKey(Client, on_delete=models.DO_NOTHING) integration_name = models.CharField(max_length=100) - integration_client_id = models.CharField(max_length=100) - integration_api_key = models.CharField(max_length=100) + integration_client_id = models.CharField(max_length=100, blank=True) + integration_api_key = models.CharField(max_length=100, blank=True) models.UniqueConstraint(fields=['client_document', 'integration_name'], name='unique_integration') From 26697a4cc4e40ff5ed0cef9f016e385cd775e8aa Mon Sep 17 00:00:00 2001 From: jrrn Date: Mon, 9 Jan 2023 19:06:10 -0300 Subject: [PATCH 12/19] Check db hostname and implement models for Ifood --- src/site/api_transaction/models.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/site/api_transaction/models.py b/src/site/api_transaction/models.py index 71a83623..4fedf566 100644 --- a/src/site/api_transaction/models.py +++ b/src/site/api_transaction/models.py @@ -1,3 +1,23 @@ from django.db import models +from integrations.models import IntegrationTransaction -# Create your models here. + +# Represents a sales transaction +class Transaction(models.Model): + + api = models.ForeignKey(IntegrationTransaction, on_delete=models.PROTECT, related_name='api_source') + id = models.CharField(max_length=100, primary_key=True) + created_at = models.DateTimeField() + closed_at = models.DateTimeField() + + + + +class Ifood(models.Model): + + transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE, related_name='transaction_ifood') + disolay_id = models.CharField(max_length=10) + order_type = models.CharField(max_length=20) + order_timing = models.CharField(max_length=20) + sales_channel = models.CharField(max_length=30) + extra_info = models.CharField(max_length=100) \ No newline at end of file From bfcd9e39f2e807d6a4574718a51013e82571210a Mon Sep 17 00:00:00 2001 From: jrrn Date: Mon, 9 Jan 2023 19:07:00 -0300 Subject: [PATCH 13/19] Check hostname for DB and implements models for Ifood --- src/site/api_transaction/models.py | 10 +- src/site/erp/settings.py | 11 +- src/site/integrations/admin.py | 10 +- .../integrations/migrations/0001_initial.py | 31 ++-- .../0002_alter_integration_client_document.py | 22 --- .../integrations/migrations/0002_initial.py | 22 +++ ...egration_integration_client_id_and_more.py | 23 --- src/site/integrations/models.py | 21 ++- src/site/user/migrations/0001_initial.py | 157 ++++-------------- .../0002_alter_userclient_user_document.py | 23 --- ...emove_userclient_user_document_and_more.py | 22 --- src/site/user/models.py | 8 +- 12 files changed, 114 insertions(+), 246 deletions(-) delete mode 100644 src/site/integrations/migrations/0002_alter_integration_client_document.py create mode 100644 src/site/integrations/migrations/0002_initial.py delete mode 100644 src/site/integrations/migrations/0003_integration_integration_client_id_and_more.py delete mode 100644 src/site/user/migrations/0002_alter_userclient_user_document.py delete mode 100644 src/site/user/migrations/0003_remove_userclient_user_document_and_more.py diff --git a/src/site/api_transaction/models.py b/src/site/api_transaction/models.py index 4fedf566..c5e5adc5 100644 --- a/src/site/api_transaction/models.py +++ b/src/site/api_transaction/models.py @@ -11,13 +11,17 @@ class Transaction(models.Model): closed_at = models.DateTimeField() - +################################## +# IFOOD # +################################## class Ifood(models.Model): transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE, related_name='transaction_ifood') - disolay_id = models.CharField(max_length=10) + display_id = models.CharField(max_length=10) order_type = models.CharField(max_length=20) order_timing = models.CharField(max_length=20) sales_channel = models.CharField(max_length=30) - extra_info = models.CharField(max_length=100) \ No newline at end of file + extra_info = models.CharField(max_length=100) + + \ No newline at end of file diff --git a/src/site/erp/settings.py b/src/site/erp/settings.py index f96bd18b..0eaeb8b2 100644 --- a/src/site/erp/settings.py +++ b/src/site/erp/settings.py @@ -69,13 +69,22 @@ # Database # https://docs.djangoproject.com/en/4.1/ref/settings/#databases +# Check host name (to validate if running Docker) +host = os.system("ping -c 1 db") +if host == 0: + print("Running Docker") + host = "db" +else: + print(host) + host = "localhost" + DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": "db", "USER": "user", "PASSWORD": "password", - "HOST": "db", + "HOST": host, "PORT": "5432", } } diff --git a/src/site/integrations/admin.py b/src/site/integrations/admin.py index c8739214..b032b593 100644 --- a/src/site/integrations/admin.py +++ b/src/site/integrations/admin.py @@ -1,4 +1,10 @@ from django.contrib import admin -from .models import Integration +from .models import Integration, IntegrationTransaction -admin.site.register(Integration) + +class IntegrationTransactionInline(admin.StackedInline): + model = IntegrationTransaction + +@admin.register(Integration) +class IntegrationAdmin(admin.ModelAdmin): + inlines = [IntegrationTransactionInline] diff --git a/src/site/integrations/migrations/0001_initial.py b/src/site/integrations/migrations/0001_initial.py index 8ab9e7a8..e1563045 100644 --- a/src/site/integrations/migrations/0001_initial.py +++ b/src/site/integrations/migrations/0001_initial.py @@ -1,30 +1,31 @@ -# Generated by Django 4.1.4 on 2022-12-27 20:32 +# Generated by Django 4.1.5 on 2023-01-09 17:18 from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): initial = True - dependencies = [] + dependencies = [ + ] operations = [ migrations.CreateModel( - name="Integration", + name='Integration', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('integration_name', models.CharField(max_length=100)), + ('status', models.CharField(max_length=30)), + ], + ), + migrations.CreateModel( + name='IntegrationTransaction', fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("client_document", models.CharField(max_length=14)), - ("integration_name", models.CharField(max_length=100)), - ("integration_api_key", models.CharField(max_length=100)), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('identifier', models.CharField(max_length=100)), + ('integration', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='integration_transaction', to='integrations.integration')), ], ), ] diff --git a/src/site/integrations/migrations/0002_alter_integration_client_document.py b/src/site/integrations/migrations/0002_alter_integration_client_document.py deleted file mode 100644 index f77f5e36..00000000 --- a/src/site/integrations/migrations/0002_alter_integration_client_document.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.1.4 on 2022-12-27 21:04 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("user", "0002_alter_userclient_user_document"), - ("integrations", "0001_initial"), - ] - - operations = [ - migrations.AlterField( - model_name="integration", - name="client_document", - field=models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, to="user.client" - ), - ), - ] diff --git a/src/site/integrations/migrations/0002_initial.py b/src/site/integrations/migrations/0002_initial.py new file mode 100644 index 00000000..03138b28 --- /dev/null +++ b/src/site/integrations/migrations/0002_initial.py @@ -0,0 +1,22 @@ +# Generated by Django 4.1.5 on 2023-01-09 17:18 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('integrations', '0001_initial'), + ('user', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='integration', + name='client_document', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='user.client'), + ), + ] diff --git a/src/site/integrations/migrations/0003_integration_integration_client_id_and_more.py b/src/site/integrations/migrations/0003_integration_integration_client_id_and_more.py deleted file mode 100644 index 3d71abd4..00000000 --- a/src/site/integrations/migrations/0003_integration_integration_client_id_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.1.5 on 2023-01-07 15:51 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('integrations', '0002_alter_integration_client_document'), - ] - - operations = [ - migrations.AddField( - model_name='integration', - name='integration_client_id', - field=models.CharField(blank=True, max_length=100), - ), - migrations.AlterField( - model_name='integration', - name='integration_api_key', - field=models.CharField(blank=True, max_length=100), - ), - ] diff --git a/src/site/integrations/models.py b/src/site/integrations/models.py index 980c54c5..6f9fbeaa 100644 --- a/src/site/integrations/models.py +++ b/src/site/integrations/models.py @@ -1,15 +1,26 @@ from django.db import models from user.models import Client -# Create your models here. + class Integration(models.Model): - client_document = models.ForeignKey(Client, on_delete=models.DO_NOTHING) + client_document = models.ForeignKey(Client, on_delete=models.PROTECT) integration_name = models.CharField(max_length=100) - integration_client_id = models.CharField(max_length=100, blank=True) - integration_api_key = models.CharField(max_length=100, blank=True) + status = models.CharField(max_length=30) models.UniqueConstraint(fields=['client_document', 'integration_name'], name='unique_integration') def __str__(self): - return self.integration_name \ No newline at end of file + return f'{self.client_document} - {self.integration_name}' + + def __eq__(self, other): + return self.client_document == other.client_document and self.integration_name == other.integration_name + + def __hash__(self): + return hash((self.client_document, self.integration_name)) + + +class IntegrationTransaction(models.Model): + + integration = models.ForeignKey(Integration, on_delete=models.PROTECT, related_name='integration_transaction') + identifier = models.CharField(max_length=100) \ No newline at end of file diff --git a/src/site/user/migrations/0001_initial.py b/src/site/user/migrations/0001_initial.py index f56bde84..60f40697 100644 --- a/src/site/user/migrations/0001_initial.py +++ b/src/site/user/migrations/0001_initial.py @@ -1,9 +1,8 @@ -# Generated by Django 4.1.4 on 2022-12-27 20:32 +# Generated by Django 4.1.5 on 2023-01-09 17:18 import django.contrib.auth.models import django.contrib.auth.validators from django.db import migrations, models -import django.db.models.deletion import django.utils.timezone @@ -12,141 +11,47 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ("auth", "0012_alter_user_first_name_max_length"), + ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ migrations.CreateModel( - name="Client", + name='Client', fields=[ - ( - "document", - models.CharField(max_length=14, primary_key=True, serialize=False), - ), - ("nome_fantasia", models.CharField(max_length=100)), - ("razao_social", models.CharField(max_length=100)), + ('document', models.CharField(max_length=14, primary_key=True, serialize=False)), + ('nome_fantasia', models.CharField(max_length=100)), + ('razao_social', models.CharField(max_length=100)), ], ), migrations.CreateModel( - name="UserClient", + name='UserClient', fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("password", models.CharField(max_length=128, verbose_name="password")), - ( - "last_login", - models.DateTimeField( - blank=True, null=True, verbose_name="last login" - ), - ), - ( - "is_superuser", - models.BooleanField( - default=False, - help_text="Designates that this user has all permissions without explicitly assigning them.", - verbose_name="superuser status", - ), - ), - ( - "username", - models.CharField( - error_messages={ - "unique": "A user with that username already exists." - }, - help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", - max_length=150, - unique=True, - validators=[ - django.contrib.auth.validators.UnicodeUsernameValidator() - ], - verbose_name="username", - ), - ), - ( - "first_name", - models.CharField( - blank=True, max_length=150, verbose_name="first name" - ), - ), - ( - "last_name", - models.CharField( - blank=True, max_length=150, verbose_name="last name" - ), - ), - ( - "email", - models.EmailField( - blank=True, max_length=254, verbose_name="email address" - ), - ), - ( - "is_staff", - models.BooleanField( - default=False, - help_text="Designates whether the user can log into this admin site.", - verbose_name="staff status", - ), - ), - ( - "is_active", - models.BooleanField( - default=True, - help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", - verbose_name="active", - ), - ), - ( - "date_joined", - models.DateTimeField( - default=django.utils.timezone.now, verbose_name="date joined" - ), - ), - ("whatsapp_number", models.CharField(max_length=20, unique=True)), - ("whatsapp_chat_id", models.CharField(max_length=20, unique=True)), - ("telegram_username", models.CharField(max_length=40, unique=True)), - ("telegram_chat_id", models.CharField(max_length=20, unique=True)), - ( - "groups", - models.ManyToManyField( - blank=True, - help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", - related_name="user_set", - related_query_name="user", - to="auth.group", - verbose_name="groups", - ), - ), - ( - "user_document", - models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, to="user.client" - ), - ), - ( - "user_permissions", - models.ManyToManyField( - blank=True, - help_text="Specific permissions for this user.", - related_name="user_set", - related_query_name="user", - to="auth.permission", - verbose_name="user permissions", - ), - ), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('whatsapp_number', models.CharField(blank=True, max_length=20, unique=True)), + ('whatsapp_chat_id', models.CharField(blank=True, max_length=20, unique=True)), + ('telegram_username', models.CharField(blank=True, max_length=40, unique=True)), + ('telegram_chat_id', models.CharField(blank=True, max_length=20, unique=True)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_document', models.ManyToManyField(to='user.client')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), ], options={ - "verbose_name": "user", - "verbose_name_plural": "users", - "abstract": False, + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, }, - managers=[("objects", django.contrib.auth.models.UserManager()),], + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], ), ] diff --git a/src/site/user/migrations/0002_alter_userclient_user_document.py b/src/site/user/migrations/0002_alter_userclient_user_document.py deleted file mode 100644 index 0e805565..00000000 --- a/src/site/user/migrations/0002_alter_userclient_user_document.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.1.4 on 2022-12-27 20:41 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("user", "0001_initial"), - ] - - operations = [ - migrations.AlterField( - model_name="userclient", - name="user_document", - field=models.ForeignKey( - default="1", - on_delete=django.db.models.deletion.DO_NOTHING, - to="user.client", - ), - ), - ] diff --git a/src/site/user/migrations/0003_remove_userclient_user_document_and_more.py b/src/site/user/migrations/0003_remove_userclient_user_document_and_more.py deleted file mode 100644 index 261ef1e2..00000000 --- a/src/site/user/migrations/0003_remove_userclient_user_document_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.1.5 on 2023-01-06 22:32 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('user', '0002_alter_userclient_user_document'), - ] - - operations = [ - migrations.RemoveField( - model_name='userclient', - name='user_document', - ), - migrations.AddField( - model_name='userclient', - name='user_document', - field=models.ManyToManyField(to='user.client'), - ), - ] diff --git a/src/site/user/models.py b/src/site/user/models.py index ca2af831..e38adb87 100644 --- a/src/site/user/models.py +++ b/src/site/user/models.py @@ -16,11 +16,11 @@ class UserClient(AbstractUser): user_document = models.ManyToManyField(Client) - whatsapp_number = models.CharField(max_length=20, unique=True) - whatsapp_chat_id = models.CharField(max_length=20, unique=True) + whatsapp_number = models.CharField(max_length=20, unique=True, blank=True) + whatsapp_chat_id = models.CharField(max_length=20, unique=True, blank=True) - telegram_username = models.CharField(max_length=40, unique=True) - telegram_chat_id = models.CharField(max_length=20, unique=True) + telegram_username = models.CharField(max_length=40, unique=True, blank=True) + telegram_chat_id = models.CharField(max_length=20, unique=True, blank=True) USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['password'] From 4ad89a05d10c5d96d4be7792319f20f8eb43cb64 Mon Sep 17 00:00:00 2001 From: jrrn Date: Mon, 9 Jan 2023 20:29:44 -0300 Subject: [PATCH 14/19] Models for API Sales --- src/site/api_transaction/models.py | 109 +++++++++++++++++- ...er_integration_client_document_and_more.py | 25 ++++ 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 src/site/integrations/migrations/0003_alter_integration_client_document_and_more.py diff --git a/src/site/api_transaction/models.py b/src/site/api_transaction/models.py index c5e5adc5..8e03e8d3 100644 --- a/src/site/api_transaction/models.py +++ b/src/site/api_transaction/models.py @@ -2,14 +2,121 @@ from integrations.models import IntegrationTransaction +class Client(models.Model): + + name = models.CharField(max_length=100, null=False, blank=False) + email = models.EmailField(blank=True) + phone_number = models.CharField(max_length=20, blank=True) + document = models.CharField(max_length=14, null=True) + group = models.CharField(max_length=100, blank=True) + id = models.CharField(max_length=100, primary_key=True) + + + +class Product(models.Model): + + id = models.CharField(max_length=100, primary_key=True) + name = models.CharField(max_length=100, null=True) + description = models.CharField(max_length=100, null=True) + image_url = models.URLField(null=True) + + + +class ProductSale(models.Model): + + quantity = models.FloatField(null=False, blank=False) + unit = models.CharField(max_length=10, null=False, blank=False) + + unit_price = models.FloatField(null=False, blank=False) + addition = models.FloatField(null=False, blank=False) + + # Price = Quantity * (unit_price + addition) + price = models.FloatField(null=False, blank=False) + + options_price = models.FloatField(null=False, blank=False) + + # Total Price = price + options_price + total_price = models.FloatField(null=False, blank=False) + + observations = models.CharField(max_length=100, null=True) + + + +class Address(models.Model): + + street_name = models.CharField(max_length=100, null=True) + street_number = models.CharField(max_length=10, null=True) + neighborhood = models.CharField(max_length=100, null=True) + complement = models.CharField(max_length=100, null=True) + reference = models.CharField(max_length=100, null=True) + postal_code = models.CharField(max_length=10, null=True) + city = models.CharField(max_length=100, null=True) + country = models.CharField(max_length=50, null=True) + latitude = models.CharField(max_length=15, null=True) + longitude = models.CharField(max_length=15, null=True) + + + +class Delivery(models.Model): + + # Mode = DEFAULT / EXPRESS / ECONOMIC + mode = models.CharField(max_length=20, null=True) + + # Delivery_by = IFOOD / MERCHANT / CORREIOS + delivery_by = models.CharField(max_length=20, null=True) + + delivery_date_time = models.DateTimeField(null=True) + observations = models.CharField(max_length=100, null=True) + address = models.ForeignKey(Address, on_delete=models.PROTECT, related_name='address_delivery', null=True) + + + +class Payment(models.Model): + + value = models.FloatField(null=False, blank=False) + currency = models.CharField(max_length=3, null=False, blank=False) + + # Type = ONLINE or OFFLINE + type = models.CharField(max_length=20, null=False, blank=False) + + # Method = CASH / CREDIT / DEBIT / PIX + method = models.CharField(max_length=20, null=False, blank=False) + + date = models.DateField() + bank_account = models.CharField(max_length=20, null=True) + + + +class Sale(models.Models): + + total_sales = models.FloatField(null=False, blank=False) + total_discount = models.FloatField(null=False, blank=False) + total_shipping = models.FloatField(null=False, blank=False) + total_products = models.FloatField(null=False, blank=False) + + client = models.ForeignKey(Client, on_delete=models.PROTECT, related_name='client_sale', null=True) + delivery = models.ForeignKey(Delivery, on_delete=models.PROTECT, related_name='delivery_sale', null=True) + products = models.ManyToManyField(Product, related_name='products_sale', null=True, through='ProductSale') + payments = models.ManyToManyField(Payment, related_name='payments_sale', null=True) + + + + + # Represents a sales transaction class Transaction(models.Model): api = models.ForeignKey(IntegrationTransaction, on_delete=models.PROTECT, related_name='api_source') - id = models.CharField(max_length=100, primary_key=True) + id_api = models.CharField(max_length=100, primary_key=True) created_at = models.DateTimeField() closed_at = models.DateTimeField() + models.UniqueConstraint(fields=['api', 'id_api'], name='unique_transaction') + + sale = models.ForeignKey(Sale, on_delete=models.PROTECT, related_name='transaction_sale') + + + ################################## # IFOOD # diff --git a/src/site/integrations/migrations/0003_alter_integration_client_document_and_more.py b/src/site/integrations/migrations/0003_alter_integration_client_document_and_more.py new file mode 100644 index 00000000..bd637ea1 --- /dev/null +++ b/src/site/integrations/migrations/0003_alter_integration_client_document_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.1.5 on 2023-01-09 23:28 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0001_initial'), + ('integrations', '0002_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='integration', + name='client_document', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='user.client'), + ), + migrations.AlterField( + model_name='integrationtransaction', + name='integration', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='integration_transaction', to='integrations.integration'), + ), + ] From a0ff4a5deb41ee37e1e91414207785d040cf5c91 Mon Sep 17 00:00:00 2001 From: jrrn Date: Mon, 9 Jan 2023 21:54:55 -0300 Subject: [PATCH 15/19] update sales and Ifood models --- src/site/api_transaction/models.py | 42 +++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/site/api_transaction/models.py b/src/site/api_transaction/models.py index 8e03e8d3..51850bee 100644 --- a/src/site/api_transaction/models.py +++ b/src/site/api_transaction/models.py @@ -59,7 +59,7 @@ class Address(models.Model): class Delivery(models.Model): - # Mode = DEFAULT / EXPRESS / ECONOMIC + # Mode = DEFAULT / EXPRESS / ECONOMIC / TAKE OUT mode = models.CharField(max_length=20, null=True) # Delivery_by = IFOOD / MERCHANT / CORREIOS @@ -85,6 +85,31 @@ class Payment(models.Model): date = models.DateField() bank_account = models.CharField(max_length=20, null=True) + details = models.CharField(max_length=100, null=True) + + + +class Benefit(models.Model): + + value = models.FloatField(null=False, blank=False) + + # Target = CART / DELIVERY_FEE / ITEM / PROGRESSIVE + target = models.CharField(max_length=20, null=False, blank=False) + + # SPONSORSHIP = IFOOD / MERCHANT / EXTERNAL + sponsorship_name = models.CharField(max_length=20, null=False, blank=False) + sponsorship_description = models.CharField(max_length=100, null=False, blank=False) + + + +class Fee(models.Model): + + # Type = SMALL_ORDER + type = models.CharField(max_length=100, null=False) + + full_description = models.CharField(max_length=100, null=False) + value = models.FloatField(null=False, blank=False) + liabilities = models.CharField(max_length=200, null=False) class Sale(models.Models): @@ -98,6 +123,12 @@ class Sale(models.Models): delivery = models.ForeignKey(Delivery, on_delete=models.PROTECT, related_name='delivery_sale', null=True) products = models.ManyToManyField(Product, related_name='products_sale', null=True, through='ProductSale') payments = models.ManyToManyField(Payment, related_name='payments_sale', null=True) + benefits = models.ManyToManyField(Benefit, related_name='benefits_sale', null=True) + fees = models.ManyToManyField(Fee, related_name='fees_sale', null=True) + + order_amount = models.FloatField(null=False, blank=False) + prepaid_amount = models.FloatField(null=False, blank=False) + pending_amount = models.FloatField(null=False, blank=False) @@ -122,6 +153,12 @@ class Transaction(models.Model): # IFOOD # ################################## + +class IfoodClient(models.Model): + + orders_count = models.IntegerField(null=True) + segmentation = models.CharField(max_length=20, null=True) + class Ifood(models.Model): transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE, related_name='transaction_ifood') @@ -131,4 +168,7 @@ class Ifood(models.Model): sales_channel = models.CharField(max_length=30) extra_info = models.CharField(max_length=100) + ifood_client_details = models.ForeignKey(IfoodClient, on_delete=models.PROTECT, related_name='ifood_client_details', null=True) + + \ No newline at end of file From b49314916b64e8bebdbbfe1866c3e672a836134d Mon Sep 17 00:00:00 2001 From: jrrn Date: Mon, 9 Jan 2023 23:07:40 -0300 Subject: [PATCH 16/19] update models --- .../migrations/0001_initial.py | 147 ++++++++++++++++++ .../migrations/0002_initial.py | 82 ++++++++++ src/site/api_transaction/models.py | 29 ++-- src/site/erp/settings.py | 1 + .../integrations/migrations/0001_initial.py | 4 +- .../integrations/migrations/0002_initial.py | 4 +- ...er_integration_client_document_and_more.py | 25 --- src/site/user/migrations/0001_initial.py | 2 +- 8 files changed, 251 insertions(+), 43 deletions(-) create mode 100644 src/site/api_transaction/migrations/0001_initial.py create mode 100644 src/site/api_transaction/migrations/0002_initial.py delete mode 100644 src/site/integrations/migrations/0003_alter_integration_client_document_and_more.py diff --git a/src/site/api_transaction/migrations/0001_initial.py b/src/site/api_transaction/migrations/0001_initial.py new file mode 100644 index 00000000..e90cfcd1 --- /dev/null +++ b/src/site/api_transaction/migrations/0001_initial.py @@ -0,0 +1,147 @@ +# Generated by Django 4.1.5 on 2023-01-10 01:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Address', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('street_name', models.CharField(max_length=100, null=True)), + ('street_number', models.CharField(max_length=10, null=True)), + ('neighborhood', models.CharField(max_length=100, null=True)), + ('complement', models.CharField(max_length=100, null=True)), + ('reference', models.CharField(max_length=100, null=True)), + ('postal_code', models.CharField(max_length=10, null=True)), + ('city', models.CharField(max_length=100, null=True)), + ('country', models.CharField(max_length=50, null=True)), + ('latitude', models.CharField(max_length=15, null=True)), + ('longitude', models.CharField(max_length=15, null=True)), + ], + ), + migrations.CreateModel( + name='Benefit', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('value', models.FloatField()), + ('target', models.CharField(max_length=20)), + ('sponsorship_name', models.CharField(max_length=20)), + ('sponsorship_description', models.CharField(max_length=100)), + ], + ), + migrations.CreateModel( + name='Client', + fields=[ + ('name', models.CharField(max_length=100)), + ('email', models.EmailField(blank=True, max_length=254)), + ('phone_number', models.CharField(blank=True, max_length=20)), + ('document', models.CharField(max_length=14, null=True)), + ('group', models.CharField(blank=True, max_length=100)), + ('id', models.CharField(max_length=100, primary_key=True, serialize=False)), + ], + ), + migrations.CreateModel( + name='Delivery', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('mode', models.CharField(max_length=20, null=True)), + ('delivery_by', models.CharField(max_length=20, null=True)), + ('delivery_date_time', models.DateTimeField(null=True)), + ('observations', models.CharField(max_length=100, null=True)), + ], + ), + migrations.CreateModel( + name='Fee', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.CharField(max_length=100)), + ('full_description', models.CharField(max_length=100)), + ('value', models.FloatField()), + ('liabilities', models.CharField(max_length=200)), + ], + ), + migrations.CreateModel( + name='Ifood', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('display_id', models.CharField(max_length=10)), + ('order_type', models.CharField(max_length=20)), + ('order_timing', models.CharField(max_length=20)), + ('sales_channel', models.CharField(max_length=30)), + ('extra_info', models.CharField(max_length=100)), + ], + ), + migrations.CreateModel( + name='IfoodClient', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('orders_count', models.IntegerField(null=True)), + ('segmentation', models.CharField(max_length=20, null=True)), + ], + ), + migrations.CreateModel( + name='Payment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('value', models.FloatField()), + ('currency', models.CharField(max_length=3)), + ('type', models.CharField(max_length=20)), + ('method', models.CharField(max_length=20)), + ('date', models.DateField()), + ('bank_account', models.CharField(max_length=20, null=True)), + ('details', models.CharField(max_length=100, null=True)), + ], + ), + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.CharField(max_length=100, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100, null=True)), + ('description', models.CharField(max_length=100, null=True)), + ('image_url', models.URLField(null=True)), + ], + ), + migrations.CreateModel( + name='ProductSale', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.FloatField()), + ('unit', models.CharField(max_length=10)), + ('unit_price', models.FloatField()), + ('addition', models.FloatField()), + ('price', models.FloatField()), + ('options_price', models.FloatField()), + ('total_price', models.FloatField()), + ('observations', models.CharField(max_length=100, null=True)), + ], + ), + migrations.CreateModel( + name='Sale', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('total_sales', models.FloatField()), + ('total_discount', models.FloatField()), + ('total_shipping', models.FloatField()), + ('total_products', models.FloatField()), + ('order_amount', models.FloatField()), + ('prepaid_amount', models.FloatField()), + ('pending_amount', models.FloatField()), + ], + ), + migrations.CreateModel( + name='Transaction', + fields=[ + ('api_id', models.CharField(max_length=100, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField()), + ('closed_at', models.DateTimeField()), + ], + ), + ] diff --git a/src/site/api_transaction/migrations/0002_initial.py b/src/site/api_transaction/migrations/0002_initial.py new file mode 100644 index 00000000..ed08c143 --- /dev/null +++ b/src/site/api_transaction/migrations/0002_initial.py @@ -0,0 +1,82 @@ +# Generated by Django 4.1.5 on 2023-01-10 01:43 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('integrations', '0001_initial'), + ('api_transaction', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='transaction', + name='api_source', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='integrations.integrationtransaction'), + ), + migrations.AddField( + model_name='transaction', + name='sale', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='api_transaction.sale'), + ), + migrations.AddField( + model_name='sale', + name='benefits', + field=models.ManyToManyField(to='api_transaction.benefit'), + ), + migrations.AddField( + model_name='sale', + name='client', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='api_transaction.client'), + ), + migrations.AddField( + model_name='sale', + name='delivery', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='api_transaction.delivery'), + ), + migrations.AddField( + model_name='sale', + name='fees', + field=models.ManyToManyField(to='api_transaction.fee'), + ), + migrations.AddField( + model_name='sale', + name='payments', + field=models.ManyToManyField(to='api_transaction.payment'), + ), + migrations.AddField( + model_name='sale', + name='products', + field=models.ManyToManyField(through='api_transaction.ProductSale', to='api_transaction.product'), + ), + migrations.AddField( + model_name='productsale', + name='product', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='api_transaction.product'), + ), + migrations.AddField( + model_name='productsale', + name='sale', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='api_transaction.sale'), + ), + migrations.AddField( + model_name='ifood', + name='ifood_client_details', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='api_transaction.ifoodclient'), + ), + migrations.AddField( + model_name='ifood', + name='transaction', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api_transaction.transaction'), + ), + migrations.AddField( + model_name='delivery', + name='address', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='api_transaction.address'), + ), + ] diff --git a/src/site/api_transaction/models.py b/src/site/api_transaction/models.py index 51850bee..5d310111 100644 --- a/src/site/api_transaction/models.py +++ b/src/site/api_transaction/models.py @@ -24,6 +24,8 @@ class Product(models.Model): class ProductSale(models.Model): + product = models.ForeignKey(Product, on_delete=models.PROTECT) + sale = models.ForeignKey('Sale', on_delete=models.PROTECT) quantity = models.FloatField(null=False, blank=False) unit = models.CharField(max_length=10, null=False, blank=False) @@ -67,7 +69,7 @@ class Delivery(models.Model): delivery_date_time = models.DateTimeField(null=True) observations = models.CharField(max_length=100, null=True) - address = models.ForeignKey(Address, on_delete=models.PROTECT, related_name='address_delivery', null=True) + address = models.ForeignKey(Address, on_delete=models.PROTECT, null=True) @@ -111,20 +113,21 @@ class Fee(models.Model): value = models.FloatField(null=False, blank=False) liabilities = models.CharField(max_length=200, null=False) + -class Sale(models.Models): +class Sale(models.Model): total_sales = models.FloatField(null=False, blank=False) total_discount = models.FloatField(null=False, blank=False) total_shipping = models.FloatField(null=False, blank=False) total_products = models.FloatField(null=False, blank=False) - client = models.ForeignKey(Client, on_delete=models.PROTECT, related_name='client_sale', null=True) - delivery = models.ForeignKey(Delivery, on_delete=models.PROTECT, related_name='delivery_sale', null=True) - products = models.ManyToManyField(Product, related_name='products_sale', null=True, through='ProductSale') - payments = models.ManyToManyField(Payment, related_name='payments_sale', null=True) - benefits = models.ManyToManyField(Benefit, related_name='benefits_sale', null=True) - fees = models.ManyToManyField(Fee, related_name='fees_sale', null=True) + client = models.ForeignKey(Client, on_delete=models.PROTECT, null=True) + delivery = models.ForeignKey(Delivery, on_delete=models.PROTECT, null=True) + products = models.ManyToManyField(Product, through='ProductSale') + payments = models.ManyToManyField(Payment) + benefits = models.ManyToManyField(Benefit) + fees = models.ManyToManyField(Fee) order_amount = models.FloatField(null=False, blank=False) prepaid_amount = models.FloatField(null=False, blank=False) @@ -137,14 +140,14 @@ class Sale(models.Models): # Represents a sales transaction class Transaction(models.Model): - api = models.ForeignKey(IntegrationTransaction, on_delete=models.PROTECT, related_name='api_source') - id_api = models.CharField(max_length=100, primary_key=True) + api_source = models.ForeignKey(IntegrationTransaction, on_delete=models.PROTECT) + api_id = models.CharField(max_length=100, primary_key=True) created_at = models.DateTimeField() closed_at = models.DateTimeField() models.UniqueConstraint(fields=['api', 'id_api'], name='unique_transaction') - sale = models.ForeignKey(Sale, on_delete=models.PROTECT, related_name='transaction_sale') + sale = models.ForeignKey(Sale, on_delete=models.PROTECT) @@ -161,14 +164,14 @@ class IfoodClient(models.Model): class Ifood(models.Model): - transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE, related_name='transaction_ifood') + transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE) display_id = models.CharField(max_length=10) order_type = models.CharField(max_length=20) order_timing = models.CharField(max_length=20) sales_channel = models.CharField(max_length=30) extra_info = models.CharField(max_length=100) - ifood_client_details = models.ForeignKey(IfoodClient, on_delete=models.PROTECT, related_name='ifood_client_details', null=True) + ifood_client_details = models.ForeignKey(IfoodClient, on_delete=models.PROTECT, null=True) \ No newline at end of file diff --git a/src/site/erp/settings.py b/src/site/erp/settings.py index 0eaeb8b2..8bc564ed 100644 --- a/src/site/erp/settings.py +++ b/src/site/erp/settings.py @@ -33,6 +33,7 @@ "django.contrib.staticfiles", "user", "integrations", + "api_transaction" ] MIDDLEWARE = [ diff --git a/src/site/integrations/migrations/0001_initial.py b/src/site/integrations/migrations/0001_initial.py index e1563045..b72fe91e 100644 --- a/src/site/integrations/migrations/0001_initial.py +++ b/src/site/integrations/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.1.5 on 2023-01-09 17:18 +# Generated by Django 4.1.5 on 2023-01-10 01:43 from django.db import migrations, models import django.db.models.deletion @@ -25,7 +25,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('identifier', models.CharField(max_length=100)), - ('integration', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='integration_transaction', to='integrations.integration')), + ('integration', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='integration_transaction', to='integrations.integration')), ], ), ] diff --git a/src/site/integrations/migrations/0002_initial.py b/src/site/integrations/migrations/0002_initial.py index 03138b28..3458bb65 100644 --- a/src/site/integrations/migrations/0002_initial.py +++ b/src/site/integrations/migrations/0002_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.1.5 on 2023-01-09 17:18 +# Generated by Django 4.1.5 on 2023-01-10 01:43 from django.db import migrations, models import django.db.models.deletion @@ -17,6 +17,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='integration', name='client_document', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='user.client'), + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='user.client'), ), ] diff --git a/src/site/integrations/migrations/0003_alter_integration_client_document_and_more.py b/src/site/integrations/migrations/0003_alter_integration_client_document_and_more.py deleted file mode 100644 index bd637ea1..00000000 --- a/src/site/integrations/migrations/0003_alter_integration_client_document_and_more.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.1.5 on 2023-01-09 23:28 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('user', '0001_initial'), - ('integrations', '0002_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='integration', - name='client_document', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='user.client'), - ), - migrations.AlterField( - model_name='integrationtransaction', - name='integration', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='integration_transaction', to='integrations.integration'), - ), - ] diff --git a/src/site/user/migrations/0001_initial.py b/src/site/user/migrations/0001_initial.py index 60f40697..b19eb0f2 100644 --- a/src/site/user/migrations/0001_initial.py +++ b/src/site/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.1.5 on 2023-01-09 17:18 +# Generated by Django 4.1.5 on 2023-01-10 01:43 import django.contrib.auth.models import django.contrib.auth.validators From d64191f787ef950a062c5004b6842f149d3a3b9c Mon Sep 17 00:00:00 2001 From: jrrn Date: Wed, 11 Jan 2023 19:26:21 -0300 Subject: [PATCH 17/19] Sales saving in DB --- src/functions/bd_update_sales/api_ifood.py | 31 +++++++---- src/functions/bd_update_sales/bd_sales.py | 51 ++++++++++++++++--- .../0003_alter_transaction_closed_at.py | 18 +++++++ ...nt_document_alter_client_email_and_more.py | 33 ++++++++++++ ...0005_rename_total_sales_sale_total_fees.py | 18 +++++++ src/site/api_transaction/models.py | 14 ++--- src/site/integrations/models.py | 3 -- 7 files changed, 141 insertions(+), 27 deletions(-) create mode 100644 src/site/api_transaction/migrations/0003_alter_transaction_closed_at.py create mode 100644 src/site/api_transaction/migrations/0004_alter_client_document_alter_client_email_and_more.py create mode 100644 src/site/api_transaction/migrations/0005_rename_total_sales_sale_total_fees.py diff --git a/src/functions/bd_update_sales/api_ifood.py b/src/functions/bd_update_sales/api_ifood.py index d7dbd94f..2e2bbbc1 100644 --- a/src/functions/bd_update_sales/api_ifood.py +++ b/src/functions/bd_update_sales/api_ifood.py @@ -32,10 +32,16 @@ def __init__(self): ############################### def connect(self): + print(f'Getting Access Token from {self.api_name}...') - if self.accessToken != None: - return + try: + if os.environ.get('IFOOD_ACCESS_TOKEN') != "": + self.configure_headers(self.accessToken) + return + except: + pass + URL = f'{BASE_URL}/authentication/v1.0/oauth/token' data={ @@ -47,6 +53,7 @@ def connect(self): post = requests.post(URL, data=data) self.accessToken = post.json()['accessToken'] + os.environ['IFOOD_ACCESS_TOKEN'] = str(self.accessToken) self.configure_headers(self.accessToken) print(f'\tAccess Token obtained!') @@ -66,13 +73,14 @@ def download(self) -> bool: print(f'Downloading data from {self.api_name}...') # MERCHANTS - self.merchants = self.download_merchants() + #self.merchants = self.download_merchants() #self.merchants_details = self.download_merchants_details(merchants) - self.merchants_hash_downloaded = hashlib.md5(str(self.merchants).encode('utf-8')).hexdigest() + #self.merchants_hash_downloaded = hashlib.md5(str(self.merchants).encode('utf-8')).hexdigest() # ORDERS - self.orders = self.download_orders() - self.orders_details = self.download_orders_details(self.orders) + orders = self.download_orders() + if orders != None: + self.orders_details = self.download_orders_details(orders) return True @@ -140,12 +148,12 @@ def save_db(self): db = DbSalesIfood() - if self.merchants_hash_downloaded != self.merchants_hash_saved: - db.insert_merchants(self.merchants) - self.merchants_hash_saved = self.merchants_hash_downloaded + #if self.merchants_hash_downloaded != self.merchants_hash_saved: + #db.insert_merchants(self.merchants) + #self.merchants_hash_saved = self.merchants_hash_downloaded - if self.orders != None: - db.insert_orders(self.orders) + if self.orders_details != None: + db.insert_orders(self.orders_details) def send_acks(): @@ -176,6 +184,7 @@ def start(self): except Exception as e: print(f'Error: {e}') + os.environ['IFOOD_ACCESS_TOKEN'] = "" raise e diff --git a/src/functions/bd_update_sales/bd_sales.py b/src/functions/bd_update_sales/bd_sales.py index e9ce4e7d..39f90777 100644 --- a/src/functions/bd_update_sales/bd_sales.py +++ b/src/functions/bd_update_sales/bd_sales.py @@ -45,7 +45,8 @@ def execute(self, command, values): except: self.bd_return = None - print(f'Executed: {command} with values: {values} -> Result: {self.bd_return}') + print(f'Executed: [{command}] -> Result: {self.bd_return}') + self.commit() return self.bd_return except Exception as e: @@ -57,7 +58,7 @@ def execute(self, command, values): def commit(self): try: - self.db.commit() + self.connection.commit() return True except Exception as e: @@ -84,6 +85,7 @@ def insert_merchants(self, merchants): print(f'Inserting merchant: {merchant}') + db.disconnect() @@ -91,11 +93,48 @@ def insert_merchants(self, merchants): def insert_orders(self, orders): - db = DbSales() - db.connect() + self.db = DbSales() + self.db.connect() for order in orders: - print(f'Inserting order: {order}') + self.insert_sale(order) + api_source_id = self.get_source_id(order) + + self.db.disconnect() + + + + def get_source_id(self, order): + return order['merchant']['id'] + + + + def insert_sale(self, order): + + total = order['total'] + + total_products = total['subTotal'] + total_shipping = total['deliveryFee'] + total_discount = total['benefits'] + total_fees = total['additionalFees'] + order_amount = total['orderAmount'] + + pre_paid_amount = order['payments']['prepaid'] + pending_amount = order['payments']['pending'] + + db_table = 'api_transaction_sale' + + self.db.execute(f'INSERT INTO {db_table} (total_products, total_shipping, total_discount, total_fees, order_amount, prepaid_amount, pending_amount) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id', (total_products, total_shipping, total_discount, total_fees, order_amount, pre_paid_amount, pending_amount)) + + + + def insert_transaction_base(self, api_source_id, order): + pass + + + def insert_transaction_client(): + pass - db.disconnect() \ No newline at end of file + def insert_transaction_delivery(): + pass \ No newline at end of file diff --git a/src/site/api_transaction/migrations/0003_alter_transaction_closed_at.py b/src/site/api_transaction/migrations/0003_alter_transaction_closed_at.py new file mode 100644 index 00000000..606e1a0f --- /dev/null +++ b/src/site/api_transaction/migrations/0003_alter_transaction_closed_at.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.5 on 2023-01-11 20:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api_transaction', '0002_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='transaction', + name='closed_at', + field=models.DateTimeField(null=True), + ), + ] diff --git a/src/site/api_transaction/migrations/0004_alter_client_document_alter_client_email_and_more.py b/src/site/api_transaction/migrations/0004_alter_client_document_alter_client_email_and_more.py new file mode 100644 index 00000000..0d3bf26a --- /dev/null +++ b/src/site/api_transaction/migrations/0004_alter_client_document_alter_client_email_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.1.5 on 2023-01-11 20:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api_transaction', '0003_alter_transaction_closed_at'), + ] + + operations = [ + migrations.AlterField( + model_name='client', + name='document', + field=models.CharField(max_length=14, null=True, unique=True), + ), + migrations.AlterField( + model_name='client', + name='email', + field=models.EmailField(max_length=254, null=True, unique=True), + ), + migrations.AlterField( + model_name='client', + name='group', + field=models.CharField(max_length=100, null=True), + ), + migrations.AlterField( + model_name='client', + name='phone_number', + field=models.CharField(max_length=20, null=True, unique=True), + ), + ] diff --git a/src/site/api_transaction/migrations/0005_rename_total_sales_sale_total_fees.py b/src/site/api_transaction/migrations/0005_rename_total_sales_sale_total_fees.py new file mode 100644 index 00000000..3fff591e --- /dev/null +++ b/src/site/api_transaction/migrations/0005_rename_total_sales_sale_total_fees.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.5 on 2023-01-11 21:35 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api_transaction', '0004_alter_client_document_alter_client_email_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='sale', + old_name='total_sales', + new_name='total_fees', + ), + ] diff --git a/src/site/api_transaction/models.py b/src/site/api_transaction/models.py index 5d310111..7789cf23 100644 --- a/src/site/api_transaction/models.py +++ b/src/site/api_transaction/models.py @@ -5,10 +5,10 @@ class Client(models.Model): name = models.CharField(max_length=100, null=False, blank=False) - email = models.EmailField(blank=True) - phone_number = models.CharField(max_length=20, blank=True) - document = models.CharField(max_length=14, null=True) - group = models.CharField(max_length=100, blank=True) + email = models.EmailField(null=True, unique=True) + phone_number = models.CharField(max_length=20, null=True, unique=True) + document = models.CharField(max_length=14, null=True, unique=True) + group = models.CharField(max_length=100, null=True) id = models.CharField(max_length=100, primary_key=True) @@ -117,10 +117,10 @@ class Fee(models.Model): class Sale(models.Model): - total_sales = models.FloatField(null=False, blank=False) + total_products = models.FloatField(null=False, blank=False) total_discount = models.FloatField(null=False, blank=False) total_shipping = models.FloatField(null=False, blank=False) - total_products = models.FloatField(null=False, blank=False) + total_fees = models.FloatField(null=False, blank=False) client = models.ForeignKey(Client, on_delete=models.PROTECT, null=True) delivery = models.ForeignKey(Delivery, on_delete=models.PROTECT, null=True) @@ -143,7 +143,7 @@ class Transaction(models.Model): api_source = models.ForeignKey(IntegrationTransaction, on_delete=models.PROTECT) api_id = models.CharField(max_length=100, primary_key=True) created_at = models.DateTimeField() - closed_at = models.DateTimeField() + closed_at = models.DateTimeField(null=True) models.UniqueConstraint(fields=['api', 'id_api'], name='unique_transaction') diff --git a/src/site/integrations/models.py b/src/site/integrations/models.py index 6f9fbeaa..37708ff4 100644 --- a/src/site/integrations/models.py +++ b/src/site/integrations/models.py @@ -13,9 +13,6 @@ class Integration(models.Model): def __str__(self): return f'{self.client_document} - {self.integration_name}' - def __eq__(self, other): - return self.client_document == other.client_document and self.integration_name == other.integration_name - def __hash__(self): return hash((self.client_document, self.integration_name)) From 357aaeff44088fabce672b043c0c1c3985a22d15 Mon Sep 17 00:00:00 2001 From: jrrn Date: Wed, 11 Jan 2023 20:47:36 -0300 Subject: [PATCH 18/19] Add data to sales tables --- src/functions/bd_update_sales/api_ifood.py | 36 +++++++++------------- src/functions/bd_update_sales/bd_sales.py | 34 ++++++++++++++------ 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/functions/bd_update_sales/api_ifood.py b/src/functions/bd_update_sales/api_ifood.py index 2e2bbbc1..d9dcd5a8 100644 --- a/src/functions/bd_update_sales/api_ifood.py +++ b/src/functions/bd_update_sales/api_ifood.py @@ -36,26 +36,22 @@ def connect(self): print(f'Getting Access Token from {self.api_name}...') try: - if os.environ.get('IFOOD_ACCESS_TOKEN') != "": - self.configure_headers(self.accessToken) + if self.accessToken != None: return - except: - pass - - - URL = f'{BASE_URL}/authentication/v1.0/oauth/token' - data={ - 'clientId': self.CLIENT_ID, - 'clientSecret': self.CLIENT_SECRET, - 'grantType': 'client_credentials' - } - - post = requests.post(URL, data=data) + + URL = f'{BASE_URL}/authentication/v1.0/oauth/token' + data={ + 'clientId': self.CLIENT_ID, + 'clientSecret': self.CLIENT_SECRET, + 'grantType': 'client_credentials' + } + + post = requests.post(URL, data=data) + self.accessToken = post.json()['accessToken'] - self.accessToken = post.json()['accessToken'] - os.environ['IFOOD_ACCESS_TOKEN'] = str(self.accessToken) - self.configure_headers(self.accessToken) - print(f'\tAccess Token obtained!') + finally: + self.configure_headers(self.accessToken) + print(f'\tAccess Token obtained!') @@ -79,8 +75,7 @@ def download(self) -> bool: # ORDERS orders = self.download_orders() - if orders != None: - self.orders_details = self.download_orders_details(orders) + self.orders_details = self.download_orders_details(orders) if orders != None else None return True @@ -184,7 +179,6 @@ def start(self): except Exception as e: print(f'Error: {e}') - os.environ['IFOOD_ACCESS_TOKEN'] = "" raise e diff --git a/src/functions/bd_update_sales/bd_sales.py b/src/functions/bd_update_sales/bd_sales.py index 39f90777..441467bf 100644 --- a/src/functions/bd_update_sales/bd_sales.py +++ b/src/functions/bd_update_sales/bd_sales.py @@ -98,17 +98,13 @@ def insert_orders(self, orders): for order in orders: - self.insert_sale(order) - api_source_id = self.get_source_id(order) + id_sale = self.insert_sale(order)[0] + self.insert_transaction_base(id_sale, order) self.db.disconnect() - def get_source_id(self, order): - return order['merchant']['id'] - - def insert_sale(self, order): @@ -125,12 +121,32 @@ def insert_sale(self, order): db_table = 'api_transaction_sale' - self.db.execute(f'INSERT INTO {db_table} (total_products, total_shipping, total_discount, total_fees, order_amount, prepaid_amount, pending_amount) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id', (total_products, total_shipping, total_discount, total_fees, order_amount, pre_paid_amount, pending_amount)) + id_sale = self.db.execute(f'INSERT INTO {db_table} (total_products, total_shipping, total_discount, total_fees, order_amount, prepaid_amount, pending_amount) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id', (total_products, total_shipping, total_discount, total_fees, order_amount, pre_paid_amount, pending_amount)) + return id_sale[0] - def insert_transaction_base(self, api_source_id, order): - pass + + def get_source_id(self, order): + + db_table = 'integrations_integrationtransaction' + merchant_id = order['merchant']['id'] + source_id = self.db.execute(f'SELECT id FROM {db_table} WHERE identifier = %s', (merchant_id,)) + return source_id[0][0] + + + + def insert_transaction_base(self, id_sale, order): + + db_table = 'api_transaction_transaction' + + created_at = order['createdAt'] + api_source_id = self.get_source_id(order) + api_id = order['id'] + + self.db.execute(f'INSERT INTO {db_table} (api_id, created_at, api_source_id, sale_id) VALUES (%s, %s, %s, %s)', (api_id, created_at, api_source_id, id_sale)) + + def insert_transaction_client(): From 6addb2574af1a6a4548093d1c7a3c8b48815be90 Mon Sep 17 00:00:00 2001 From: jrafaelrn Date: Tue, 10 Oct 2023 12:25:15 -0300 Subject: [PATCH 19/19] update site requirements --- src/site/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/site/requirements.txt b/src/site/requirements.txt index 55a3af5e..8db8ec39 100644 --- a/src/site/requirements.txt +++ b/src/site/requirements.txt @@ -4,3 +4,5 @@ Pillow psycopg2-binary coveralls python-dotenv +requests +psycopg2 \ No newline at end of file