Files
OSIT-AE-API-FastAPI/app/models/membership_model.py

471 lines
21 KiB
Python

from __future__ import annotations
import datetime, hashlib, logging, os, pytz, redis, secrets
from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator
from ..lib_general import *
from ..log import *
from .common_field_schema import base_fields, default_num_bytes
from .address_model import Address_Base
from .contact_model import Contact_Base
from .organization_model import Organization_Base
from .person_model import Person_Base
from .user_model import User_Base
class Membership_Cfg_Base(BaseModel):
app.logger.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
app.logger.debug(locals())
account_name: Optional[str]
cycle_type: Optional[str]
membership_length: Optional[int] = Field(0, ge=0, lt=150)
prorate: Optional[bool] = False
calendar_year_start_buffer_days: Optional[int] = Field(0, ge=0, lt=150)
calendar_year_start_buffer_on: Optional[datetime.datetime] = None
calendar_year_start_on: Optional[datetime.datetime] = None
calendar_year_end_on: Optional[datetime.datetime] = None
calendar_year_end_buffer_days: Optional[int] = Field(0, ge=0, lt=150)
calendar_year_end_buffer_on: Optional[datetime.datetime] = None
enable_privacy_view: Optional[bool] = False
accept_message: Optional[str]
reject_message: Optional[str]
renew_message: Optional[str]
#cust_membership_profile: Optional[str] # list of dicts outlining custom membership profile fields for client
cust_membership_profile: Optional[Json] = '[]' # list of dicts outlining custom membership profile fields for client
default_no_reply_email: Optional[str]
default_no_reply_name: Optional[str]
confirm_email: Optional[str]
confirm_name: Optional[str]
Membership_Cfg_Base.update_forward_refs()
class Membership_Profile_Base(BaseModel):
app.logger.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
app.logger.debug(locals())
# if TYPE_CHECKING:
# from .supporting_core_models import Address_Base, Contact_Base, Organization_Base, User_Base
# from .person_model import Person_Base
# from .address_model import Address_Base
# from .contact_model import Contact_Base
# from .organization_model import Organization_Base
# from .person_model import Person_Base
# from .user_model import User_Base
id_random: Optional[str] = Field(
**base_fields['membership_profile_id_random'],
alias='membership_profile_id_random',
default_factory=lambda:secrets.token_urlsafe(default_num_bytes),
)
id: Optional[int] = Field(
#alias='membership_profile_id'
)
membership_id_random: Optional[str]
membership_id: Optional[int]
person_id_random: Optional[str]
person_id: Optional[int]
user_id_random: Optional[str]
user_id: Optional[int]
organization_id_random: Optional[str]
organization_id: Optional[int]
contact_id_random: Optional[str]
contact_id: Optional[int]
address_id_random: Optional[str]
address_id: Optional[int]
display_name: Optional[str]
email: Optional[str]
email_allowed: Optional[bool]
email_newsletter: Optional[bool]
mail_newsletter: Optional[bool]
show_online: Optional[bool]
show_printed: Optional[bool]
biography: Optional[str]
notes: Optional[str]
created_on: Optional[datetime.datetime] = None
updated_on: Optional[datetime.datetime] = None
address: 'Optional[Address_Base]' = Address_Base()
contact: 'Optional[Contact_Base]' = Contact_Base()
organization: 'Optional[Organization_Base]' = Organization_Base()
person: 'Optional[Person_Base]' = Person_Base()
user: 'Optional[User_Base]' = User_Base()
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
#@validator('membership_profile_id_random', always=True)
def membership_profile_id_random_copy(cls, v, values, **kwargs):
app.logger.setLevel(logging.WARNING)
app.logger.debug(locals())
if values['id_random']:
return values['id_random']
return None
@validator('id', always=True)
def membership_profile_id_lookup(cls, v, values, **kwargs):
app.logger.setLevel(logging.WARNING)
app.logger.debug(locals())
if values['id_random']:
app.logger.debug(values['id_random'])
return redis_lookup_id_random(record_id_random=values['id_random'], table_name='membership_profile')
return None
@validator('membership_id', always=True)
def membership_lookup(cls, v, values, **kwargs):
app.logger.setLevel(logging.WARNING)
app.logger.debug(locals())
if values['membership_id_random']:
return redis_lookup_id_random(record_id_random=values['membership_id_random'], table_name='membership')
return None
@validator('person_id', always=True)
def person_id_lookup(cls, v, values, **kwargs):
app.logger.setLevel(logging.WARNING)
app.logger.debug(locals())
if values['person_id_random']:
return redis_lookup_id_random(record_id_random=values['person_id_random'], table_name='person')
return None
@validator('user_id', always=True)
def user_id_lookup(cls, v, values, **kwargs):
app.logger.setLevel(logging.WARNING)
app.logger.debug(locals())
if values['user_id_random']:
return redis_lookup_id_random(record_id_random=values['user_id_random'], table_name='user')
return None
class Config:
underscore_attrs_are_private = True
fields = base_fields
Membership_Profile_Base.update_forward_refs()
class Membership_Base(BaseModel):
app.logger.setLevel(logging.WARNING)
app.logger.debug(locals())
#from .supporting_core_models import User_Base
# from .account_model import Account_Base
# from .person_model import Person_Base
# from .user_model import User_Base
id_random: Optional[str] = Field(
**base_fields['membership_id_random'],
alias='membership_id_random',
default_factory=lambda:secrets.token_urlsafe(default_num_bytes),
)
id: Optional[int] = Field(
#alias='membership_id'
)
account_id_random: Optional[str]
account_id: Optional[int] # NOTE: This is not really optional
person_id_random: Optional[str]
person_id: Optional[int]
user_id_random: Optional[str]
user_id: Optional[int]
level_number: Optional[int] = Field(0, ge=0, lt=150)
level_name: Optional[str]
product_id: Optional[int]
type_id: Optional[int]
type_name: Optional[str]
category_id: Optional[int]
category_name: Optional[str]
division_id: Optional[int]
division_name: Optional[str]
status_id: Optional[int]
status_name: Optional[str]
application_start_on: Optional[datetime.datetime] = None
approved_on: Optional[datetime.datetime] = None
first_start_on: Optional[datetime.datetime] = None
start_buffer_on: Optional[datetime.datetime] = None
start_on: Optional[datetime.datetime] = None
end_on: Optional[datetime.datetime] = None
end_buffer_on: Optional[datetime.datetime] = None
flag: Optional[bool]
flag_message: Optional[str]
notes: Optional[str]
created_on: Optional[datetime.datetime] = None
updated_on: Optional[datetime.datetime] = None
#account: Optional[Account_Base] = Account_Base()
profile: 'Optional[Membership_Profile_Base]' = Membership_Profile_Base()
person: 'Optional[Person_Base]' = Person_Base()
user: 'Optional[User_Base]' = User_Base()
cfg: 'Optional[Membership_Cfg_Base]' = Membership_Cfg_Base()
cust_profile: Optional[dict] = {}
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
#@validator('order_id_random', always=True)
def order_id_random_copy(cls, v, values, **kwargs):
app.logger.setLevel(logging.WARNING)
app.logger.debug(locals())
if values['id_random']:
return values['id_random']
return None
@validator('id', always=True)
def membership_id_lookup(cls, v, values, **kwargs):
app.logger.setLevel(logging.WARNING)
app.logger.debug(locals())
if values['id_random']:
return redis_lookup_id_random(record_id_random=values['id_random'], table_name='membership')
return None
@validator('account_id', always=True)
def account_id_lookup(cls, v, values, **kwargs):
app.logger.setLevel(logging.WARNING)
app.logger.debug(locals())
if values['account_id_random']:
return redis_lookup_id_random(record_id_random=values['account_id_random'], table_name='account')
return None
@validator('person_id', always=True)
def person_id_lookup(cls, v, values, **kwargs):
app.logger.setLevel(logging.WARNING)
app.logger.debug(locals())
if values['person_id_random']:
return redis_lookup_id_random(record_id_random=values['person_id_random'], table_name='person')
return None
@validator('user_id', always=True)
def user_id_lookup(cls, v, values, **kwargs):
app.logger.setLevel(logging.WARNING)
app.logger.debug(locals())
if values['user_id_random']:
return redis_lookup_id_random(record_id_random=values['user_id_random'], table_name='user')
return None
class Config:
underscore_attrs_are_private = True
fields = base_fields
# Membership_Base.update_forward_refs()
# ### BEGIN ### API Membership Model ### save_membership_obj() ###
def save_membership_obj(order_obj_new:Membership_Base=None):
app.logger.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
app.logger.debug(locals())
if not order_obj_new:
return False
app.logger.debug(order_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True))
order_line_obj_li_curr = [] # Initialize to store order_line list
if order_obj_new.id_random:
app.logger.info(f'An order.id {order_obj_new.id} or order.id_random {order_obj_new.id_random} was included. We can update an existing order.')
app.logger.info(f'Get the current order_line list to compare with what was sent...')
data = {}
data['order_id_random'] = order_obj_new.id_random
if order_line_rec_li_curr := sql_select(table_name='v_order_line', data=data, rm_id_random=True, as_list=True):
#app.logger.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
app.logger.debug(order_line_rec_li_curr)
for order_line_rec in order_line_rec_li_curr:
try:
order_line_obj = Order_Line_Base(**order_line_rec)
app.logger.debug(order_line_obj)
except ValidationError as e:
app.logger.error(e.json())
order_line_obj_li_curr.append(order_line_obj)
else:
app.logger.info(f'No order_line records were found')
elif order_obj_new.account_id_random and (order_obj_new.person_id_random or order_obj_new.user_id_random):
app.logger.info(f'An account.id_random {order_obj_new.account_id_random} was passed. And either a person.id_random {order_obj_new.person_id_random} or user.id_random {order_obj_new.user_id_random} was passed. We can create a new order.')
# Because there was not an order ID, assume there are no order lines yet. So no look up.
else:
app.logger.info('Either an order ID is required to update an order or an account ID along with a person ID or user ID is required to create an order.')
return False
#app.logger.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
app.logger.debug(order_line_obj_li_curr)
if repl_order_line_list: # This will remove any order line list items not sent with the new order information.
app.logger.info('Removing any order line list items not sent with the new order information...')
for index, order_line_obj_curr in enumerate(order_line_obj_li_curr):
app.logger.info(f'Current: order line ID={order_line_obj_curr.id_random} and product ID= {order_line_obj_curr.product_id_random}')
matched_product_id = False
for order_line_obj_new in order_obj_new.order_line_list:
app.logger.debug(f'Checking new: product ID={order_line_obj_new.product_id_random}')
if order_line_obj_curr.product_id_random == order_line_obj_new.product_id_random:
matched_product_id = True
app.logger.debug(f'Matched: product ID={order_line_obj_new.product_id_random}')
break
else:
app.logger.debug(f'No match: product ID={order_line_obj_new.product_id_random}')
if not matched_product_id: # Was not found in the new order line list sent
app.logger.info(f'Current order line product ID did not match any of the new list. DELETE order line ID {order_line_obj_curr.id_random} with product ID {order_line_obj_curr.product_id_random}')
if order_line_del_result := sql_delete(table_name='order_line', record_id_random=order_line_obj_curr.id_random):
app.logger.info(f'Deleted record and now pop the current list item {index}...')
order_line_obj_li_curr.pop(index)
else:
app.logger.info(f'Current order line product ID matched. Keeping order line ID {order_line_obj_curr.id_random} with product ID {order_line_obj_curr.product_id_random}')
# NOTE: That this current order line item will be updated below.
app.logger.debug(order_line_obj_li_curr)
#app.logger.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
app.logger.info('Loop through the line list that was sent and compare with what was pulled from the DB')
# Loop through the new line list that was sent and compare with the current line list that was pulled from the DB
# Only insert if a product ID does not match
# Only update if a product ID does match
for order_line_obj_new in order_obj_new.order_line_list:
app.logger.info(f'New: order line ID={order_line_obj_new.id_random} and product ID= {order_line_obj_new.product_id_random}')
matched_product_id = False
for index, order_line_obj_curr in enumerate(order_line_obj_li_curr):
app.logger.debug(f'Checking current: product ID={order_line_obj_curr.product_id_random}')
if order_line_obj_new.product_id_random == order_line_obj_curr.product_id_random:
matched_product_id = True
app.logger.debug(f'Matched: product ID={order_line_obj_curr.product_id_random}')
app.logger.info(f'Updating the current line item with the new line item.')
order_line_obj_new.id_random = order_line_obj_curr.id_random
order_line_obj_new.id = order_line_obj_curr.id
order_line_obj_li_curr[index] = order_line_obj_new
break
else:
app.logger.debug(f'No match: product ID={order_line_obj_curr.product_id_random}')
if not matched_product_id: # Was not found in the current order line list that was pulled from the DB
app.logger.info(f'New order line product ID did not match any of the current list. Append order line ID {order_line_obj_new.id_random} with product ID {order_line_obj_new.product_id_random}')
app.logger.info('Append to current list...')
order_line_obj_li_curr.append(order_line_obj_new) # These will be inserted/updated below
# Save merged current and new list to the new order object
order_obj_new.order_line_list = order_line_obj_li_curr
app.logger.debug(order_obj_new)
# Final loop through to get the new order totals
# Calculate totals
order_total_amount:int = 0
order_total_quantity:int = 0
for order_line_obj_new in order_obj_new.order_line_list:
order_total_amount += order_line_obj_new.quantity * order_line_obj_new.amount
order_total_quantity += order_line_obj_new.quantity
order_obj_new.total_bill = order_total_amount # "amount" is used by order_cart and Stripe
order_obj_new.total_quantity = order_total_quantity
order_obj_new.balance = order_total_amount - order_obj_new.total_paid
app.logger.debug(order_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'order_line_list', 'cfg', 'created_on', 'updated_on'}))
order_obj_data = order_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'order_line_list', 'cfg', 'created_on', 'updated_on'})
# SQL INSERT or UPDATE the order record
app.logger.info('SQL INSERT or UPDATE the order record')
if order_obj_resp := sql_insert_or_update(data=order_obj_data, table_name='order', rm_id_random=True, id_random_length=8): pass
else: return False
#app.logger.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
app.logger.debug(order_obj_resp)
if isinstance(order_obj_resp, bool) and order_obj_resp:
if order_id := order_obj_new.id: pass
elif order_id := order_obj_new.id_random: pass
elif isinstance(order_obj_resp, int):
order_id = order_obj_resp
else:
return False
app.logger.debug(f'Order ID={order_id}')
# Loop through the order_line list to SQL INSERT or UPDATE the records
app.logger.info('Loop through the order_line list to SQL INSERT or UPDATE the records')
for order_line_obj_new in order_obj_new.order_line_list:
app.logger.info(f"New order_line: order_line_id_random={order_line_obj_new.id_random}; product_id_random={order_line_obj_new.product_id_random}")
app.logger.debug(order_line_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=False, exclude={'order_line_id_random', 'product_type_id', 'product_type', 'created_on', 'updated_on'}))
order_line_obj_data = order_line_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'order_line_id_random', 'product_type_id', 'product_type', 'created_on', 'updated_on'})
order_line_obj_data['order_id'] = order_id
if order_line_obj_resp := sql_insert_or_update(sql=None, data=order_line_obj_data, table_name='order_line', rm_id_random=True, id_random_length=8): pass
else: return False
app.logger.debug(order_line_obj_resp)
return order_id
# ### END ### API Membership Model ### save_membership_obj() ###
# ### BEGIN ### API Membership Model ### get_membership_obj() ###
def get_membership_obj(membership_id=None, inc_membership_profile=None, inc_membership_cfg=None, inc_cust_profile=None):
app.logger.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
app.logger.debug(locals())
if membership_id := redis_lookup_id_random(record_id_random=membership_id, table_name='membership'): pass
else:
return False
if membership_rec := sql_select(table_name='v_membership', record_id=membership_id):
#app.logger.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
app.logger.debug(membership_rec)
if inc_membership_profile:
if membership_profile_rec := sql_select(table_name='v_membership_profile', field_name='membership_id', field_value=membership_id):
#app.logger.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
app.logger.debug(membership_profile_rec)
membership_rec['profile'] = membership_profile_rec
if inc_membership_cfg:
if membership_cfg_rec := sql_select(table_name='v_membership_cfg', field_name='account_id', field_value=membership_rec.get('account_id', None)):
#app.logger.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
app.logger.debug(membership_cfg_rec)
membership_rec['cfg'] = membership_cfg_rec
if inc_cust_profile:
account_code = membership_rec.get('account_code', None)
table_name = f'c_{account_code}_membership_profile'
if cust_profile_rec := sql_select(table_name=table_name, field_name='membership_id', field_value=membership_id):
#app.logger.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
app.logger.debug(cust_profile_rec)
membership_rec['cust_profile'] = cust_profile_rec
#app.logger.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
app.logger.debug(membership_rec)
else:
return False
try:
membership_obj = Membership_Base(**membership_rec)
app.logger.debug(membership_obj)
except ValidationError as e:
app.logger.error(e.json())
return membership_obj
# ### END ### API Membership Model ### get_membership_obj() ###