mirror of
https://git.auk.su/Dinect/bonus-import-tools.git
synced 2025-12-28 18:10:01 +00:00
added docker-compose.yaml and Dockerfile
This commit is contained in:
@@ -3,4 +3,4 @@ POS_TOKEN='123456'
|
||||
PRODUCTION=1
|
||||
CURRENCY='RUS'
|
||||
COUNTRY='RU'
|
||||
DRY_RUN=1
|
||||
DRY_RUN=
|
||||
147
app.py
147
app.py
@@ -1,26 +1,63 @@
|
||||
#!/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')
|
||||
|
||||
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):
|
||||
@@ -34,6 +71,7 @@ for f in files:
|
||||
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()
|
||||
|
||||
134
dinect_api.py
134
dinect_api.py
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
Reference in New Issue
Block a user