You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
5.7 KiB
242 lines
5.7 KiB
import enum |
|
from datetime import datetime, timedelta |
|
from collections import namedtuple |
|
|
|
VendorAggregate = namedtuple("VendorAggregate", ["name", "terms"]) |
|
|
|
|
|
@enum.unique |
|
class OrderStatus(enum.Enum): |
|
OPEN = 1 |
|
APPROVAL = 2 |
|
ORDERED = 3 |
|
COMPLETED = 4 |
|
HOLD = 5 |
|
|
|
|
|
@enum.unique |
|
class OrderCategory(enum.Enum): |
|
DISPOSABLE = 1 |
|
SOLVENT = 2 |
|
CHEMICAL = 3 |
|
BIOLAB = 4 |
|
SYNTHESIS = 5 |
|
|
|
|
|
@enum.unique |
|
class UserRole(enum.Enum): |
|
NEW = 1 |
|
USER = 2 |
|
PURCHASER = 3 |
|
ADMIN = 4 |
|
INACTIVE = 5 |
|
|
|
@property |
|
def principal(self): |
|
"""returns the principal identifier of the role""" |
|
return "role:" + self.name.lower() |
|
|
|
|
|
class Model: |
|
def __hash__(self): |
|
items = sorted(self.__dict__.items()) |
|
no_log = ((k, v) for k, v in items if not k == "log") |
|
content = ((k, v) for k, v in no_log if not k.startswith("_")) |
|
return hash(tuple(content)) |
|
|
|
def __eq__(self, other): |
|
return hash(self) == hash(other) |
|
|
|
|
|
class OrderItem(Model): |
|
"""an ordered item""" |
|
|
|
# properties |
|
id = None |
|
cas_description = None |
|
catalog_nr = None |
|
vendor = None |
|
category = None |
|
package_size = None |
|
unit_price = None |
|
currency = None |
|
amount = None |
|
account = None |
|
comment = None |
|
|
|
# redundant properties, could be determined from orders log |
|
created_on = None |
|
created_by = None |
|
status = None |
|
|
|
def __init__( |
|
self, |
|
id, |
|
cas_description, |
|
catalog_nr, |
|
vendor, |
|
category, |
|
package_size, |
|
unit_price, |
|
amount, |
|
currency="", |
|
account="", |
|
comment="", |
|
created_on=None, |
|
created_by=None, |
|
status=None, |
|
): |
|
self.id = id |
|
self.cas_description = cas_description |
|
self.catalog_nr = catalog_nr |
|
self.vendor = vendor |
|
self.category = category |
|
self.package_size = package_size |
|
self.unit_price = unit_price |
|
self.amount = amount |
|
self.currency = currency |
|
self.account = account |
|
self.comment = comment |
|
self.created_on = created_on |
|
self.created_by = created_by |
|
self.status = status |
|
|
|
self.log = [] |
|
|
|
def __repr__(self): |
|
return f"<ordr3.models.OrderItem id={self.id}>" |
|
|
|
@property |
|
def total_price(self): |
|
return self.unit_price * self.amount |
|
|
|
@property |
|
def in_process(self): |
|
return self.status not in (OrderStatus.OPEN, OrderStatus.HOLD) |
|
|
|
def add_to_log(self, log_entry): |
|
"""adds a log item to the status log""" |
|
if len(self.log) == 0: |
|
self.created_by = log_entry.by |
|
self.created_on = log_entry.date |
|
self.status = log_entry.status |
|
self.log.append(log_entry) |
|
|
|
|
|
class LogEntry(Model): |
|
"""an entry in the order log""" |
|
|
|
order_id = None |
|
status = None |
|
by = None |
|
date = None |
|
|
|
def __init__(self, order_id, status, by, date=None): |
|
self.order_id = order_id |
|
self.status = status |
|
self.by = by |
|
self.date = date or datetime.utcnow() |
|
|
|
def __repr__(self): |
|
return ( |
|
f"<ordr3.models.LogEntry({self.order_id}, " |
|
f"{self.status}, {self.by}, {self.date})>" |
|
) |
|
|
|
|
|
class ProposedConsumable: |
|
"""counting orders to find out if they are consumables""" |
|
|
|
def __init__(self, order): |
|
self.order = order |
|
self.times = 0 |
|
|
|
|
|
class Vendor(Model): |
|
"""a model for finding vendor names and their search terms""" |
|
|
|
term = None |
|
name = None |
|
|
|
def __init__(self, term, name): |
|
self.term = term |
|
self.name = name |
|
|
|
def __repr__(self): |
|
return f"<ordr3.models.Vendor term={self.term} name={self.name}>" |
|
|
|
|
|
class User(Model): |
|
|
|
id = None |
|
username = None |
|
first_name = None |
|
last_name = None |
|
email = None |
|
password = None |
|
role = None |
|
|
|
def __init__( |
|
self, id, username, first_name, last_name, email, password, role |
|
): |
|
self.id = id |
|
self.username = username |
|
self.first_name = first_name |
|
self.last_name = last_name |
|
self.email = email |
|
self.password = password |
|
self.role = role |
|
|
|
@property |
|
def principal(self): |
|
return f"user:{self.username}" |
|
|
|
@property |
|
def principals(self): |
|
tmp = [self.principal] |
|
if self.role in {UserRole.PURCHASER, UserRole.ADMIN}: |
|
tmp.append(UserRole.USER.principal) |
|
if self.role == UserRole.ADMIN: |
|
tmp.append(UserRole.PURCHASER.principal) |
|
tmp.append(self.role.principal) |
|
return tmp |
|
|
|
@property |
|
def is_active(self): |
|
"""check if it is an active user account""" |
|
return self.role in {UserRole.USER, UserRole.PURCHASER, UserRole.ADMIN} |
|
|
|
def __hash__(self): |
|
items = sorted(self.__dict__.items()) |
|
content = ((k, v) for k, v in items if not k.startswith("_")) |
|
return hash(tuple(content)) |
|
|
|
def __repr__(self): |
|
return f"<ordr3.models.User id={self.id}, username={self.username}>" |
|
|
|
|
|
class PasswordResetToken(Model): |
|
|
|
token = None |
|
user_id = None |
|
valid_until = None |
|
|
|
def __init__(self, token, user_id, valid_until=None): |
|
self.token = token |
|
self.user_id = user_id |
|
defaul_valid_until = datetime.utcnow() + timedelta(hours=1) |
|
self.valid_until = valid_until or defaul_valid_until |
|
|
|
@property |
|
def is_valid(self): |
|
try: |
|
return datetime.utcnow() < self.valid_until |
|
except TypeError: |
|
return False |
|
|
|
def __repr__(self): |
|
date_str = self.valid_until.strftime("%Y-%m-%d %H:%M:%S") |
|
return ( |
|
f"<ordr3.models.PasswordResetToken token={self.token}, " |
|
f"user_id={self.user_id}, valid_until={date_str}>" |
|
)
|
|
|