added docker-compose.yaml and Dockerfile

This commit is contained in:
Sergey
2024-08-10 12:47:30 +03:00
parent 6ad5e18879
commit 48b0f23bf3
5 changed files with 262 additions and 204 deletions

View File

@@ -3,4 +3,4 @@ POS_TOKEN='123456'
PRODUCTION=1 PRODUCTION=1
CURRENCY='RUS' CURRENCY='RUS'
COUNTRY='RU' COUNTRY='RU'
DRY_RUN=1 DRY_RUN=

319
app.py
View File

@@ -1,140 +1,247 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# __author__ = 'szhdanoff@gmail.com' # __author__ = 'szhdanoff@gmail.com'
__version__ = '1.0.1'
import os import os
import time import time
import csv import csv
import phonenumbers import phonenumbers
import apscheduler
#
from apscheduler.schedulers.background import BackgroundScheduler
from email_validator import validate_email, EmailNotValidError from email_validator import validate_email, EmailNotValidError
from dotenv import load_dotenv from dotenv import load_dotenv
# local imports # local imports
from dinect_api import get_user from dinect_api import get_user
from dinect_api import new_user
from dinect_api import new_user_by_card from dinect_api import new_user_by_card
from dinect_api import bonuses_update
load_dotenv() load_dotenv()
is_prod = bool(os.getenv('PRODUCTION', False))
COUNTRY = os.getenv('COUNTRY', 'RU') COUNTRY = os.getenv('COUNTRY', 'RU')
DRY_RUN = bool(os.getenv('DRY_RUN', False))
csv_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'csv') csv_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'csv')
files = [] print('Script version:', __version__, '| CSV path:', csv_path, '| COUNTRY:', COUNTRY, '| DRY_RUN:', DRY_RUN)
# r=root, d=directories, f = files
for r, d, f in os.walk(csv_path):
for file in f:
filename, file_extension = os.path.splitext(file)
if file_extension == '.csv':
files.append(os.path.join(r, file))
for f in files:
with open(f + f'-{time.strftime("%Y%m%d-%H%M%S", time.localtime())}.log', "w", encoding="utf-8") as log_file:
with open(f, "r", encoding="utf-8") as csv_file:
file_name = os.path.basename(f)
# USERS file
if 'users' in file_name:
print(f'Processing "users" file: {f}')
csv_reader = csv.reader(csv_file, delimiter=',')
line_count = 0
for row in csv_reader:
if line_count == 0:
line_count += 1
else:
try:
print(f'Processing line {line_count}: {row}')
nickname, full_name, card, phone, email, gender = row
# strip whitespaces
nickname = nickname.strip()
full_name = full_name.strip()
card = card.strip()
# validate phone
phone = phone.strip()
try:
parsed_phone = phonenumbers.parse(phone, region=COUNTRY)
if phonenumbers.is_valid_number(parsed_phone):
phone = phonenumbers.format_number(parsed_phone, phonenumbers.PhoneNumberFormat.E164)
except:
print(f'error in line: [{line_count}]- Invalid phone number: {phone}')
log_file.write(f'error in line: [{line_count}]- Invalid phone number: {phone}\n')
# validate email
email = email.strip()
try:
email_info = validate_email(email, check_deliverability=False)
email = email_info.normalized
except EmailNotValidError as e:
print(f'error in line: [{line_count}]- Invalid email: {email}')
log_file.write(f'error in line: [{line_count}]- Invalid email: {email}\n')
email = None
# set email if not found
if email is None:
email = f'{card}@user.dinect.com'
# validate / set gender def validate_phone(raw_phone):
gender = gender.strip() """
if gender not in ['M', 'F']: Validates a given phone number.
gender = 1
else: Args:
if gender == 'M': raw_phone (str): The phone number to be validated.
Returns:
str: The validated phone number in E164 format if valid, otherwise an empty string.
"""
raw_phone = raw_phone.strip()
try:
raw_phone = phonenumbers.parse(raw_phone, COUNTRY)
if phonenumbers.is_valid_number(raw_phone):
return phonenumbers.format_number(raw_phone, phonenumbers.PhoneNumberFormat.E164)
else:
return ''
except phonenumbers.NumberParseException:
return ''
def run_import():
"""
Imports users and transactions data from CSV files, validates the data,
creates new users if necessary, and updates user bonuses.
Parameters:
None
Returns:
None
"""
files = []
# r=root, d=directories, f = files
for r, d, f in os.walk(csv_path):
for file in f:
filename, file_extension = os.path.splitext(file)
if file_extension == '.csv':
files.append(os.path.join(r, file))
for f in files:
with open(f + f'-{time.strftime("%Y%m%d-%H%M%S", time.localtime())}.log', "w", encoding="utf-8") as log_file:
with open(f, "r", encoding="utf-8") as csv_file:
file_name = os.path.basename(f)
# USERS file
start_time = time.time()
if 'users' in file_name:
print(f'Processing "users" file: {f}')
csv_reader = csv.reader(csv_file, delimiter=',')
line_count = 0
for row in csv_reader:
if line_count == 0:
line_count += 1
else:
line_count += 1
try:
print(f'Processing line {line_count}: {row}')
nickname, full_name, card, phone, email, gender = row
# strip whitespaces
nickname = nickname.strip()
full_name = full_name.strip()
card = card.strip()
# validate phone
phone = validate_phone(phone)
if phone == '':
print(f'error in line: [{line_count}]- Invalid phone number: {phone}')
log_file.write(f'error in line: [{line_count}]- Invalid phone number: {phone}\n')
# validate email
email = email.strip()
try:
email_info = validate_email(email, check_deliverability=False)
email = email_info.normalized
except EmailNotValidError as e:
print(f'error in line: [{line_count}]- Invalid email: {email}')
log_file.write(f'error in line: [{line_count}]- Invalid email: {email}\n')
email = None
# set email if not found
if email is None:
email = f'{card}@user.dinect.com'
# validate / set gender
gender = gender.strip()
if gender not in ['M', 'F']:
gender = 1 gender = 1
else: else:
gender = 2 if gender == 'M':
gender = 1
else:
gender = 2
line_count += 1 except ValueError as e:
except ValueError as e: ret = f'Unexpected error in line: [{line_count}] {repr(e)}'
ret = f'Unexpected error in line: [{line_count}] {repr(e)}' log_file.write(f'{ret}\n')
log_file.write(f'{ret}\n')
# Find user via the API # Find user via the API
# by card # by card
user_found_by_card, user_id, user_card, purchases_url, data = get_user(card) user_found_by_card, user_id, user_card, purchases_url, data = get_user(card)
print(f'user_found_by_card {card}', user_found_by_card) print(f'user_found_by_card {card}', user_found_by_card)
# by phone # by phone
phone = phone.replace('+', '') phone = phone.replace('+', '')
if not user_found_by_card: if not user_found_by_card:
user_found_by_phone, user_id, user_card, purchases_url, data = get_user(phone, get_type='phone') user_found_by_phone, user_id, user_card, purchases_url, data = get_user(phone,
print(f'user_found_by_phone {phone}', user_found_by_phone) get_type='phone')
print(f'user_found_by_phone {phone}', user_found_by_phone)
user_found = user_found_by_card or user_found_by_phone user_found = user_found_by_card or user_found_by_phone
# create new user if not found # create new user if not found
if not user_found: if not user_found:
# user_created, data = new_user( # user_created, data = new_user(
# full_name=nickname, phone=phone, gender=1, foreign_card=None, email=email, # full_name=nickname, phone=phone, gender=1, foreign_card=None, email=email,
# ) # )
user_created, data = new_user_by_card( user_created, data = new_user_by_card(
full_name=nickname, phone=phone, gender=1, external_card=card, email=email, use_existing=False full_name=nickname, phone=phone, gender=1, external_card=card, email=email,
) use_existing=False
if user_created: )
print('User created with', data['ID'], data['DIN']) if user_created:
print('User created with', data['ID'], data['DIN'])
else:
log_file.write(f'error in line: [{line_count}]- Invalid user data: {data}\n')
else: else:
log_file.write(f'error in line: [{line_count}]- Invalid user data: {data}\n') print('User found with', data['DIN'], user_card)
else:
print('User found with', user_id, user_card, purchases_url)
end_time = time.time()
print(f'Elapsed time of USERS file processing : {end_time - start_time} seconds')
# TRANSACTIONS files
start_time = time.time()
# TRANSACTIONS file if 'transaction' in file_name:
if 'transaction' in file_name: print(f'Processing "transaction" file: {f}')
print(f'Processing "transaction" file: {f}') csv_reader = csv.reader(csv_file, delimiter=',')
csv_reader = csv.reader(csv_file, delimiter=',') line_count = 0
line_count = 0 for row in csv_reader:
for row in csv_reader: if line_count == 0:
if line_count == 0:
line_count += 1
else:
try:
print(f'Processing line {line_count}: {row}')
user_id, card, phone, summ_total, summ_discount, sum_with_discount, bonus_amount, transaction_date, transaction_time = row
line_count += 1 line_count += 1
else:
try:
line_count += 1
print(f'Processing line {line_count}: {row}')
user_id, card, phone, summ_total, summ_discount, sum_with_discount, bonus_amount, transaction_date, transaction_time, doc_id = row
except ValueError as e: if card.strip() != '':
ret = f'error in line: [{line_count}] {repr(e)}' user_found_by_card, din_id, _, _, _ = get_user(card, get_type='auto')
log_file.write(f'{ret}\n') if not user_found_by_card:
print(f'error in line: [{line_count}]- Invalid card: {card}')
log_file.write(f'error in line: [{line_count}]- Invalid card: {card}\n')
else:
print('User found with', user_id, card)
else:
user_found_by_card = False
csv_file.close() if validate_phone(phone) != '':
log_file.close() phone = phone.replace('+', '')
user_found_by_phone, din_id, _, _, _ = get_user(phone, get_type='phone')
# os.rename(f, f + '.old') if not user_found_by_phone:
print(f'error in line: [{line_count}]- Invalid phone: {phone}')
log_file.write(f'error in line: [{line_count}]- Invalid phone: {phone}\n')
else:
print('User found with', din_id, phone)
else:
user_found_by_phone = False
user_found = user_found_by_card or user_found_by_phone
if user_found:
if doc_id.strip() == '':
doc_id = f'{file_name[0:20]}-{line_count}-{card}'
else:
doc_id = doc_id.strip()
result, data = bonuses_update(
user_id=din_id,
summ_total=summ_total,
bonus_amount=bonus_amount,
doc_id=doc_id,
sum_with_discount=sum_with_discount,
sum_discount=summ_discount,
dry_run=DRY_RUN
)
if not result:
print(f'error in line: [{line_count}]- bonuses_update: {data}')
log_file.write(f'error in line: [{line_count}]- bonuses_update: {data}\n')
else:
print(f'Bonuses updated, user_id={din_id}')
else:
print(f'error in line: [{line_count}]- Invalid user: {user_id}')
log_file.write(f'error in line: [{line_count}]- Invalid user: {user_id}\n')
except ValueError as e:
ret = f'error in line: [{line_count}] {repr(e)}'
log_file.write(f'{ret}\n')
end_time = time.time()
print(f'Elapsed time of TRANSACTIONS file processing : {end_time - start_time} seconds')
csv_file.close()
log_file.close()
os.rename(f, f + '.' + time.strftime("%Y%m%d-%H%M%S") + '.old')
scheduler = BackgroundScheduler()
scheduler.start()
try:
job = scheduler.add_job(run_import, 'interval', minutes=1)
print('Import running every 1 minute. Place *.csv files in "csv" directory')
print('Press Ctrl+{0} to exit'.format('Break' if apscheduler.__version__ >= '3.0.0' else 'C'))
# This is here to simulate application activity (which keeps the main thread alive).
while True:
time.sleep(10)
except (KeyboardInterrupt, SystemExit):
# Not strictly necessary if daemonic mode is enabled but should be done if possible
scheduler.shutdown()

View File

@@ -1,29 +1,24 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# __author__ = 'szhdanoff@gmail.com' # __author__ = 'szhdanoff@gmail.com'
__version__ = '1.0.1' __version__ = '1.0.3'
import os import os
import requests import requests
import json import json
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() load_dotenv()
# local imports
# import app
is_prod = bool(os.getenv('PRODUCTION', False)) is_prod = bool(os.getenv('PRODUCTION', False))
if is_prod: if is_prod:
url = 'https://pos-api.dinect.com/20130701/' url = 'https://pos-api.dinect.com/20130701/'
else: else:
url = 'https://pos-api-ote.dinect.com/20130701/' url = 'https://pos-api-ote.dinect.com/20130701/'
print(is_prod, url) print('PRODUCTION:', is_prod, '| API URL:', url)
APP_TOKEN = os.getenv('APP_TOKEN') # APP_TOKEN = os.getenv('APP_TOKEN')
POS_TOKEN = os.getenv('POS_TOKEN') # POS_TOKEN = os.getenv('POS_TOKEN')
app_token = os.getenv('APP_TOKEN') app_token = os.getenv('APP_TOKEN')
pos_token = os.getenv('POS_TOKEN') pos_token = os.getenv('POS_TOKEN')
@@ -42,8 +37,6 @@ HEADERS = {
} }
# GET /20130701/tokens/?next=/20130701/logon
# https://pos-api.dinect.com/20130701/tokens/3b01228843d115ae8c03a4d3b20dcb545dbb228c
def get_user(search_id, get_type='auto', headers=None) -> tuple: def get_user(search_id, get_type='auto', headers=None) -> tuple:
""" """
A function to get user information based on the search_id and get_type. A function to get user information based on the search_id and get_type.
@@ -118,57 +111,27 @@ def new_user(full_name, phone, gender=1, foreign_card=None, email=None, headers=
return False, r.json() return False, r.json()
def bonuses_update( def new_user_by_card(external_card, full_name, phone, gender=1, email=None, headers=None, use_existing=True):
user_id,
summ_total,
bonus_amount,
sum_with_discount,
sum_discount,
doc_id,
headers=None):
""" """
Updates the bonuses for a user. Creates a new user by card.
Args: Args:
user_id (int): The ID of the user. external_card (str): The external card code.
summ_total (float): The total amount. full_name (str): The full name of the user.
bonus_amount (float): The bonus amount. phone (str): The phone number of the user.
sum_with_discount (float): The amount with discount. gender (int, optional): The gender of the user. Defaults to 1.
sum_discount (float): The discount amount. email (str, optional): The email of the user. Defaults to None.
doc_id (str): The document ID.
headers (dict, optional): The headers to include in the request. Defaults to None. headers (dict, optional): The headers to include in the request. Defaults to None.
use_existing (bool, optional): Whether to bind the user to an existing card. Defaults to True.
Returns: Returns:
tuple: A tuple containing a boolean indicating the success of the request and the JSON response. tuple: A tuple containing a boolean indicating the success of the request and the JSON response.
If the request is successful, the boolean is True and the JSON response is returned. If the request is successful, the boolean is True and the JSON response is returned.
If the request is unsuccessful, the boolean is False and the JSON response is returned. If the request is unsuccessful, the boolean is False and the error message is returned.
""" """
if headers is None: if headers is None:
headers = HEADERS headers = HEADERS
base_url = url + 'users/' + str(user_id) + '/purchases/'
params = {
"doc_id": doc_id,
"bonus_amount": bonus_amount,
"sum_total": summ_total,
"sum_discount": sum_discount,
"sum_with_discount ": sum_with_discount,
"commit": 'True',
"curr_iso_name": 'RUB',
"override": 'True',
# "date": '2024-08-03 12:53:07',
}
r = requests.post(base_url, headers=headers, json=params)
if r.status_code == 201:
return True, r.json()
else:
return False, r.json()
def new_user_by_card(external_card, full_name, phone, gender=1, email=None, headers=None, use_existing=True):
if headers is None:
headers = HEADERS
base_url = url + 'cards/' base_url = url + 'cards/'
params = { params = {
'full_name': full_name, 'full_name': full_name,
@@ -212,46 +175,33 @@ def add_external_card(user_id, card, headers=None):
return True, r.json() return True, r.json()
# user_date = new_user('Test2', '79039426493', email='YlEJp@example.com') def bonuses_update(
# print(user_date) user_id,
summ_total,
bonus_amount,
sum_with_discount,
sum_discount,
doc_id,
headers=None,
dry_run=False
):
if headers is None:
headers = HEADERS
# 79039426498 base_url = url + 'users/' + str(user_id) + '/purchases/'
# (True, {'DIN': 232113, 'ID': '4620011139016260791309380'}) params = {
"doc_id": doc_id,
# result, user_id, user_card, purchases_url, data = get_user('1234567890123') "bonus_amount": bonus_amount,
# result, user_id, user_card, purchases_url, data = get_user('79039426493', get_type='phone') "sum_total": summ_total,
# print(result, data) "sum_discount": sum_discount,
# if result: "sum_with_discount ": sum_with_discount,
# # user_id = data[0].get('id') "commit": 'True',
# # user_card = data[0].get('card') "curr_iso_name": 'RUB',
# # purchases_url = data[0].get('purchases_url') "override": 'True'
# print('user_id', user_id) # "date": '2024-08-03 12:53:07',
# print('user_card', user_card) }
# print('purchases_url', purchases_url) r = requests.post(base_url, headers=headers, json=params)
if r.status_code == 201:
return True, r.json()
# добавление внешней карты лояльности else:
return False, r.text
# print(get_user('4620011139016689273132009'))
# print(get_user('1234567890123'))
# print(get_user('+79039406889'))
# print(new_user())
# (True, '{"DIN":3152300,"ID":"4620011139016570939672611"}')
# print(bonuses_update(
# user_id=int('1002'),
# summ_total=100.00,
# bonus_amount=10.00,
# doc_id='test12',
# sum_with_discount=90.00,
# sum_discount=10.00,
# ))
# print(get_user('79039426493', get_type='phone'))
# (True, {'DIN': 3155239, 'ID': '4620011139016802073627661'})
# print(new_user_by_card(
# external_card='1234567891235', full_name='Test', phone='79039426495', email='123321@example.com', use_existing=False
# ))

View File

@@ -1,3 +1,3 @@
user_id, card, phone, summ_total, summ_discount, sum_with_discount, bonus_amount, transaction_date, transaction_time user_id, card, phone, summ_total, summ_discount, sum_with_discount, bonus_amount, transaction_date, transaction_time, doc_id
15689, 654897321321,+78906543210,12345.67,123.56,12222.11,121,2002-03-11,21:05:36 15689, 654897321321,+78906543210,12345.67,123.56,12222.11,121,2002-03-11,21:05:36,п123
6578, 654897321123,+78906233212,345.67,45.00,300.67,12,2002-03-12,01:05:36 6578, 654897321123,+78906233212,345.67,45.00,300.67,12,2002-03-12,01:05:36,
1 user_id card phone summ_total summ_discount sum_with_discount bonus_amount transaction_date transaction_time doc_id
2 15689 654897321321 +78906543210 12345.67 123.56 12222.11 121 2002-03-11 21:05:36 п123
3 6578 654897321123 +78906233212 345.67 45.00 300.67 12 2002-03-12 01:05:36

View File

@@ -1,4 +1,5 @@
python-dotenv~=1.0.0 python-dotenv~=1.0.0
requests~=2.32.3 requests~=2.32.3
phonenumbers~=8.13.42 phonenumbers==8.13.43
email-validator APScheduler~=3.10.4
email_validator~=2.2.0