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
CURRENCY='RUS'
COUNTRY='RU'
DRY_RUN=1
DRY_RUN=

155
app.py
View File

@@ -1,39 +1,77 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# __author__ = 'szhdanoff@gmail.com'
__version__ = '1.0.1'
import os
import time
import csv
import phonenumbers
import apscheduler
#
from apscheduler.schedulers.background import BackgroundScheduler
from email_validator import validate_email, EmailNotValidError
from dotenv import load_dotenv
# local imports
from dinect_api import get_user
from dinect_api import new_user
from dinect_api import new_user_by_card
from dinect_api import bonuses_update
load_dotenv()
is_prod = bool(os.getenv('PRODUCTION', False))
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')
files = []
# r=root, d=directories, f = files
for r, d, f in os.walk(csv_path):
print('Script version:', __version__, '| CSV path:', csv_path, '| COUNTRY:', COUNTRY, '| DRY_RUN:', DRY_RUN)
def validate_phone(raw_phone):
"""
Validates a given phone number.
Args:
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:
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=',')
@@ -42,22 +80,22 @@ for f in files:
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 = 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:
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:
@@ -81,7 +119,6 @@ for f in files:
else:
gender = 2
line_count += 1
except ValueError as e:
ret = f'Unexpected error in line: [{line_count}] {repr(e)}'
log_file.write(f'{ret}\n')
@@ -93,7 +130,8 @@ for f in files:
# by phone
phone = phone.replace('+', '')
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,
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
@@ -104,19 +142,21 @@ for f in files:
# full_name=nickname, phone=phone, gender=1, foreign_card=None, email=email,
# )
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'])
else:
log_file.write(f'error in line: [{line_count}]- Invalid user data: {data}\n')
else:
print('User found with', user_id, user_card, purchases_url)
print('User found with', data['DIN'], user_card)
end_time = time.time()
print(f'Elapsed time of USERS file processing : {end_time - start_time} seconds')
# TRANSACTIONS file
# TRANSACTIONS files
start_time = time.time()
if 'transaction' in file_name:
print(f'Processing "transaction" file: {f}')
csv_reader = csv.reader(csv_file, delimiter=',')
@@ -126,15 +166,82 @@ for f in files:
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
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
if card.strip() != '':
user_found_by_card, din_id, _, _, _ = get_user(card, get_type='auto')
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
if validate_phone(phone) != '':
phone = phone.replace('+', '')
user_found_by_phone, din_id, _, _, _ = get_user(phone, get_type='phone')
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 + '.old')
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
# -*- coding: utf-8 -*-
# __author__ = 'szhdanoff@gmail.com'
__version__ = '1.0.1'
__version__ = '1.0.3'
import os
import requests
import json
from dotenv import load_dotenv
load_dotenv()
# local imports
# import app
is_prod = bool(os.getenv('PRODUCTION', False))
if is_prod:
url = 'https://pos-api.dinect.com/20130701/'
else:
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')
POS_TOKEN = os.getenv('POS_TOKEN')
# APP_TOKEN = os.getenv('APP_TOKEN')
# POS_TOKEN = os.getenv('POS_TOKEN')
app_token = os.getenv('APP_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:
"""
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()
def bonuses_update(
user_id,
summ_total,
bonus_amount,
sum_with_discount,
sum_discount,
doc_id,
headers=None):
def new_user_by_card(external_card, full_name, phone, gender=1, email=None, headers=None, use_existing=True):
"""
Updates the bonuses for a user.
Creates a new user by card.
Args:
user_id (int): The ID of the user.
summ_total (float): The total amount.
bonus_amount (float): The bonus amount.
sum_with_discount (float): The amount with discount.
sum_discount (float): The discount amount.
doc_id (str): The document ID.
external_card (str): The external card code.
full_name (str): The full name of the user.
phone (str): The phone number of the user.
gender (int, optional): The gender of the user. Defaults to 1.
email (str, optional): The email of the user. 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:
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 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:
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/'
params = {
'full_name': full_name,
@@ -212,46 +175,33 @@ def add_external_card(user_id, card, headers=None):
return True, r.json()
# user_date = new_user('Test2', '79039426493', email='YlEJp@example.com')
# print(user_date)
def bonuses_update(
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
# (True, {'DIN': 232113, 'ID': '4620011139016260791309380'})
# result, user_id, user_card, purchases_url, data = get_user('1234567890123')
# result, user_id, user_card, purchases_url, data = get_user('79039426493', get_type='phone')
# print(result, data)
# if result:
# # user_id = data[0].get('id')
# # user_card = data[0].get('card')
# # purchases_url = data[0].get('purchases_url')
# print('user_id', user_id)
# print('user_card', user_card)
# print('purchases_url', purchases_url)
# добавление внешней карты лояльности
# 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
# ))
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.text

View File

@@ -1,3 +1,3 @@
user_id, card, phone, summ_total, summ_discount, sum_with_discount, bonus_amount, transaction_date, transaction_time
15689, 654897321321,+78906543210,12345.67,123.56,12222.11,121,2002-03-11,21:05:36
6578, 654897321123,+78906233212,345.67,45.00,300.67,12,2002-03-12,01:05:36
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,п123
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
requests~=2.32.3
phonenumbers~=8.13.42
email-validator
phonenumbers==8.13.43
APScheduler~=3.10.4
email_validator~=2.2.0