2019-11-04 01:25:17 -05:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
2020-03-29 00:10:25 -04:00
|
|
|
import copy
|
|
|
|
|
2020-04-14 01:18:20 -04:00
|
|
|
from .config import Config
|
2020-03-30 21:43:05 -04:00
|
|
|
from .hid.Credential import Credential
|
|
|
|
from .hid.DoorController import ROOT, E
|
2019-11-04 01:25:17 -05:00
|
|
|
|
2020-03-30 14:01:39 -04:00
|
|
|
|
|
|
|
class Member:
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
forename="",
|
|
|
|
surname="",
|
|
|
|
membershipWorksID="",
|
|
|
|
middleName="",
|
|
|
|
email="",
|
|
|
|
phone="",
|
|
|
|
cardholderID=None,
|
|
|
|
doorAccess=[],
|
|
|
|
credentials=set(),
|
|
|
|
levels=[],
|
|
|
|
extraLevels=[],
|
|
|
|
schedules=[],
|
|
|
|
):
|
2019-11-04 01:25:17 -05:00
|
|
|
self.forename = forename
|
|
|
|
self.surname = surname
|
|
|
|
self.membershipWorksID = membershipWorksID
|
|
|
|
self.middleName = middleName
|
|
|
|
self.email = email
|
|
|
|
self.phone = phone
|
|
|
|
self.cardholderID = cardholderID
|
|
|
|
self.doorAccess = doorAccess
|
|
|
|
self.credentials = credentials
|
|
|
|
self.levels = levels
|
|
|
|
self.schedules = schedules
|
|
|
|
|
2020-03-29 00:15:54 -04:00
|
|
|
def __str__(self):
|
2019-11-04 01:25:17 -05:00
|
|
|
return f"""Name: {self.forename} | {self.middleName} | {self.surname}
|
|
|
|
MembershipWorks ID: {self.membershipWorksID}
|
|
|
|
Email: {self.email}
|
|
|
|
Phone: {self.phone}
|
|
|
|
Cardholder ID: {self.cardholderID}
|
|
|
|
doorAccess: {self.doorAccess}
|
|
|
|
Credentials: {self.credentials}
|
|
|
|
Levels: {self.levels}
|
|
|
|
Schedules: {self.schedules}
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
2019-12-18 18:47:36 -05:00
|
|
|
class MembershipworksMember(Member):
|
2020-04-14 01:18:20 -04:00
|
|
|
def __init__(self, config, data, formerMember=False):
|
2020-03-30 14:01:39 -04:00
|
|
|
super().__init__(
|
|
|
|
data["First Name"],
|
|
|
|
data["Last Name"],
|
|
|
|
membershipWorksID=data["Account ID"],
|
|
|
|
email=data["Email"],
|
|
|
|
phone=data["Phone"],
|
|
|
|
)
|
2019-12-18 18:47:36 -05:00
|
|
|
|
2019-11-04 01:25:17 -05:00
|
|
|
if data["Access Card Number"] != "":
|
2020-03-30 14:01:39 -04:00
|
|
|
self.credentials = set(
|
|
|
|
[
|
|
|
|
Credential(
|
|
|
|
code=(
|
|
|
|
data["Access Card Facility Code"],
|
|
|
|
data["Access Card Number"],
|
|
|
|
)
|
|
|
|
)
|
|
|
|
]
|
|
|
|
)
|
2019-11-04 01:25:17 -05:00
|
|
|
else:
|
2019-12-18 18:47:36 -05:00
|
|
|
self.credentials = set()
|
2019-11-04 01:25:17 -05:00
|
|
|
|
2021-07-01 12:39:10 -04:00
|
|
|
self.onHold = (
|
|
|
|
data["Account on Hold"] != ""
|
|
|
|
or data["CMS Membership on hold"] == "CMS Membership on hold"
|
|
|
|
)
|
2020-03-30 14:01:39 -04:00
|
|
|
self.formerMember = formerMember
|
2020-03-29 00:15:54 -04:00
|
|
|
|
2020-04-14 01:18:20 -04:00
|
|
|
levels = {k: v for k, v in config.memberLevels.items() if data[k] == k}
|
2019-12-18 18:47:36 -05:00
|
|
|
self.levels = list(levels.keys())
|
|
|
|
self.schedules = list(levels.values())
|
|
|
|
|
|
|
|
self.extraLevels = {
|
2020-03-30 14:01:39 -04:00
|
|
|
schedule: sum(
|
|
|
|
(doors for prop, doors in props.items() if data[prop] == "Y"), []
|
|
|
|
)
|
2020-04-14 01:18:20 -04:00
|
|
|
for schedule, props in config.doorSpecificSchedules.items()
|
2019-12-18 18:47:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
self.doorAccess = [
|
2020-03-30 14:01:39 -04:00
|
|
|
door
|
2020-04-14 01:18:20 -04:00
|
|
|
for door, doorData in config.doors.items()
|
2019-12-18 18:47:36 -05:00
|
|
|
if data["Access " + doorData.access + "?"] == "Y"
|
|
|
|
]
|
|
|
|
|
|
|
|
def to_DoorMember(self, door):
|
|
|
|
doorLevels = [k for k, v in self.extraLevels.items() if door.name in v]
|
|
|
|
|
2020-05-17 21:32:07 -04:00
|
|
|
schedules = []
|
|
|
|
if door.name in self.doorAccess and not self.onHold and not self.formerMember:
|
2021-07-01 12:14:24 -04:00
|
|
|
schedules = self.schedules + doorLevels
|
2020-05-17 21:32:07 -04:00
|
|
|
|
2020-03-30 14:01:39 -04:00
|
|
|
dm = DoorMember(
|
|
|
|
door,
|
|
|
|
forename=self.forename,
|
|
|
|
surname=self.surname,
|
|
|
|
membershipWorksID=self.membershipWorksID,
|
|
|
|
email=self.email,
|
|
|
|
phone=self.phone,
|
|
|
|
levels=self.levels + doorLevels,
|
|
|
|
doorAccess=self.doorAccess,
|
|
|
|
credentials=self.credentials,
|
|
|
|
schedules=schedules,
|
|
|
|
)
|
2019-12-18 18:47:36 -05:00
|
|
|
|
|
|
|
return dm
|
|
|
|
|
2020-03-29 00:15:54 -04:00
|
|
|
def __str__(self):
|
2020-03-30 14:01:39 -04:00
|
|
|
return (
|
|
|
|
super().__str__()
|
|
|
|
+ f"""OnHold? {self.onHold}
|
2020-03-29 00:15:54 -04:00
|
|
|
Former Member? {self.formerMember}
|
|
|
|
"""
|
2020-03-30 14:01:39 -04:00
|
|
|
)
|
2020-03-29 00:15:54 -04:00
|
|
|
|
2019-12-18 18:47:36 -05:00
|
|
|
|
|
|
|
class DoorMember(Member):
|
|
|
|
def __init__(self, door, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.door = door
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_cardholder(cls, data, door):
|
2020-03-30 14:01:39 -04:00
|
|
|
ch = cls(
|
|
|
|
door=door,
|
|
|
|
forename=data.get("forename", ""),
|
|
|
|
surname=data.get("surname", ""),
|
|
|
|
membershipWorksID=data.attrib.get("custom2", ""),
|
|
|
|
middleName=data.attrib.get("middleName", ""),
|
|
|
|
email=data.attrib.get("email", ""),
|
|
|
|
phone=data.attrib.get("phone", ""),
|
|
|
|
cardholderID=data.attrib["cardholderID"],
|
|
|
|
)
|
2019-12-18 18:47:36 -05:00
|
|
|
|
|
|
|
ch.credentials = set(
|
2020-03-30 14:01:39 -04:00
|
|
|
Credential(hex=(c.attrib["rawCardNumber"]))
|
|
|
|
for c in data.findall("{*}Credential")
|
|
|
|
)
|
2019-12-18 18:47:36 -05:00
|
|
|
|
2020-03-30 14:01:39 -04:00
|
|
|
ch.levels = data.attrib.get("custom1", "").split("|")
|
|
|
|
ch.schedules = [r.attrib["scheduleName"] for r in data.findall("{*}Role")]
|
2019-12-18 18:47:36 -05:00
|
|
|
|
|
|
|
return ch
|
2019-11-04 01:25:17 -05:00
|
|
|
|
|
|
|
def attribs(self):
|
2020-03-30 14:01:39 -04:00
|
|
|
return {
|
|
|
|
"forename": self.forename,
|
|
|
|
"surname": self.surname,
|
|
|
|
"middleName": self.middleName,
|
|
|
|
"email": self.email,
|
|
|
|
"phone": self.phone,
|
|
|
|
"custom1": "|".join(self.levels).replace("&", "and"),
|
|
|
|
"custom2": self.membershipWorksID,
|
|
|
|
}
|
2019-11-07 15:22:50 -05:00
|
|
|
|
2019-12-18 18:47:36 -05:00
|
|
|
def make_schedules(self, schedulesMap):
|
2019-11-07 15:22:50 -05:00
|
|
|
roles = [
|
2020-03-30 14:01:39 -04:00
|
|
|
E.Role(
|
|
|
|
{
|
|
|
|
"roleID": self.cardholderID,
|
2019-11-07 15:22:50 -05:00
|
|
|
"scheduleID": schedulesMap[schedule],
|
2020-03-30 14:01:39 -04:00
|
|
|
"resourceID": "0",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
for schedule in self.schedules
|
|
|
|
]
|
2019-11-04 01:25:17 -05:00
|
|
|
|
2020-03-30 14:01:39 -04:00
|
|
|
return E.RoleSet(
|
|
|
|
{"action": "UD", "roleSetID": self.cardholderID}, E.Roles(*roles)
|
|
|
|
)
|
2019-11-04 01:25:17 -05:00
|
|
|
|
2019-11-08 15:51:44 -05:00
|
|
|
def make_credentials(self, newCredentials, cardFormats):
|
|
|
|
out = [
|
2019-11-07 15:22:50 -05:00
|
|
|
E.Credential(
|
2020-03-30 14:01:39 -04:00
|
|
|
{
|
|
|
|
"formatName": str(credential.code[0]),
|
|
|
|
"cardNumber": str(credential.code[1]),
|
|
|
|
"formatID": cardFormats[str(credential.code[0])],
|
|
|
|
"isCard": "true",
|
|
|
|
"cardholderID": self.cardholderID,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
for credential in newCredentials
|
|
|
|
]
|
2019-11-04 01:25:17 -05:00
|
|
|
|
2019-11-08 15:51:44 -05:00
|
|
|
return E.Credentials({"action": "AD"}, *out)
|
2019-11-04 01:25:17 -05:00
|
|
|
|
|
|
|
|
|
|
|
def update_door(door, members):
|
2019-11-07 15:22:50 -05:00
|
|
|
cardFormats = door.get_cardFormats()
|
2020-03-30 14:01:39 -04:00
|
|
|
cardholders = {
|
|
|
|
member.membershipWorksID: member
|
|
|
|
for member in [
|
|
|
|
DoorMember.from_cardholder(ch, door) for ch in door.get_cardholders()
|
|
|
|
]
|
|
|
|
}
|
2019-11-07 15:22:50 -05:00
|
|
|
schedulesMap = door.get_scheduleMap()
|
2020-03-30 14:01:39 -04:00
|
|
|
allCredentials = set(
|
|
|
|
Credential(hex=c.attrib["rawCardNumber"]) for c in door.get_credentials()
|
|
|
|
)
|
2019-11-04 01:25:17 -05:00
|
|
|
|
2019-11-08 16:07:26 -05:00
|
|
|
# TODO: can I combine requests?
|
2019-12-18 18:47:36 -05:00
|
|
|
for membershipworksMember in members:
|
|
|
|
member = membershipworksMember.to_DoorMember(door)
|
2020-03-29 00:10:25 -04:00
|
|
|
# cardholder did not exist, so add them
|
|
|
|
if member.membershipWorksID not in cardholders:
|
|
|
|
print("- Adding Member {member.forename} {member.surname}:")
|
|
|
|
print(f" - {member.attribs()}")
|
2020-03-30 14:01:39 -04:00
|
|
|
resp = door.doXMLRequest(
|
|
|
|
ROOT(E.Cardholders({"action": "AD"}, E.Cardholder(member.attribs())))
|
|
|
|
)
|
|
|
|
member.cardholderID = resp.find("{*}Cardholders/{*}Cardholder").attrib[
|
|
|
|
"cardholderID"
|
|
|
|
]
|
2020-03-29 00:10:25 -04:00
|
|
|
|
|
|
|
# create a dummy ch to force an update
|
|
|
|
# TODO: probably a cleaner way to do this
|
|
|
|
ch = copy.copy(member)
|
|
|
|
ch.schedules = []
|
|
|
|
ch.credentials = set()
|
|
|
|
# cardholder exists, compare contents
|
|
|
|
else:
|
|
|
|
ch = cardholders.pop(member.membershipWorksID)
|
|
|
|
member.cardholderID = ch.cardholderID
|
|
|
|
|
|
|
|
if member.attribs() != ch.attribs(): # update cardholder attributes
|
|
|
|
print(f"- Updating profile for {member.forename} {member.surname}")
|
|
|
|
print(f" - Old: {ch.attribs()}")
|
|
|
|
print(f" - New: {member.attribs()}")
|
2020-03-30 14:01:39 -04:00
|
|
|
door.doXMLRequest(
|
|
|
|
ROOT(
|
|
|
|
E.Cardholders(
|
|
|
|
{"action": "UD", "cardholderID": member.cardholderID},
|
|
|
|
E.CardHolder(member.attribs()),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
2020-03-29 00:10:25 -04:00
|
|
|
|
|
|
|
if member.credentials != ch.credentials:
|
|
|
|
print(f"- Updating card for {member.forename} {member.surname}")
|
|
|
|
print(f" - {ch.credentials} -> {member.credentials}")
|
|
|
|
|
|
|
|
oldCards = ch.credentials
|
|
|
|
newCards = member.credentials
|
|
|
|
|
|
|
|
allNewCards = set(
|
2020-03-30 14:01:39 -04:00
|
|
|
card
|
|
|
|
for m in members
|
|
|
|
if m != membershipworksMember
|
|
|
|
for card in m.credentials
|
|
|
|
)
|
2020-03-29 00:10:25 -04:00
|
|
|
|
|
|
|
# cards removed, and won't be reassigned to someone else
|
|
|
|
for card in (oldCards - newCards) - allNewCards:
|
2020-03-30 14:01:39 -04:00
|
|
|
door.doXMLRequest(
|
|
|
|
ROOT(
|
|
|
|
E.Credentials(
|
|
|
|
{
|
|
|
|
"action": "UD",
|
|
|
|
"rawCardNumber": card.hex,
|
|
|
|
"isCard": "true",
|
|
|
|
},
|
|
|
|
E.Credential({"cardholderID": ""}),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
2020-03-29 00:10:25 -04:00
|
|
|
|
|
|
|
if newCards - oldCards: # cards added
|
|
|
|
for card in newCards & allNewCards: # new card exists in another member
|
2020-03-30 14:01:39 -04:00
|
|
|
print(
|
|
|
|
[
|
|
|
|
m
|
|
|
|
for m in members
|
|
|
|
for card in m.credentials
|
|
|
|
if card in newCards
|
|
|
|
]
|
|
|
|
)
|
2020-03-29 00:10:25 -04:00
|
|
|
raise Exception(f"Duplicate Card in input data! {card}")
|
|
|
|
|
|
|
|
# card existed in door, and needs to be reassigned
|
|
|
|
for card in newCards & allCredentials:
|
2020-03-30 14:01:39 -04:00
|
|
|
door.doXMLRequest(
|
|
|
|
ROOT(
|
|
|
|
E.Credentials(
|
|
|
|
{
|
|
|
|
"action": "UD",
|
|
|
|
"rawCardNumber": card.hex,
|
|
|
|
"isCard": "true",
|
|
|
|
},
|
|
|
|
E.Credential({"cardholderID": member.cardholderID}),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
2020-03-29 00:10:25 -04:00
|
|
|
|
|
|
|
# cards that never existed, and need to be created
|
|
|
|
if newCards - allCredentials:
|
2020-03-30 14:01:39 -04:00
|
|
|
door.doXMLRequest(
|
|
|
|
ROOT(
|
|
|
|
member.make_credentials(
|
|
|
|
newCards - allCredentials, cardFormats
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
2020-03-29 00:10:25 -04:00
|
|
|
|
|
|
|
if member.schedules != ch.schedules:
|
2020-03-30 14:01:39 -04:00
|
|
|
print(
|
|
|
|
"- Updating schedule for"
|
|
|
|
+ f" {member.forename} {member.surname}:"
|
|
|
|
+ f" {ch.schedules} -> {member.schedules}"
|
|
|
|
)
|
2020-03-29 00:10:25 -04:00
|
|
|
door.doXMLRequest(ROOT(member.make_schedules(schedulesMap)))
|
2019-11-04 01:25:17 -05:00
|
|
|
|
|
|
|
# TODO: delete cardholders that are no longer members?
|
|
|
|
|
2019-11-08 16:07:26 -05:00
|
|
|
|
2019-11-04 01:25:17 -05:00
|
|
|
def main():
|
2020-04-14 01:18:20 -04:00
|
|
|
config = Config()
|
|
|
|
membershipworks = config.membershipworks
|
2020-03-30 14:01:39 -04:00
|
|
|
membershipworks_attributes = (
|
2021-07-01 12:14:24 -04:00
|
|
|
"_id,nam,phn,eml,lvl,lbl,xws,xms,xsc,xas,xfd,xac,xcf,xeh,xse"
|
2020-03-30 14:01:39 -04:00
|
|
|
)
|
2019-11-04 01:25:17 -05:00
|
|
|
|
2020-03-29 00:15:54 -04:00
|
|
|
memberData = membershipworks.get_members(
|
2020-03-30 14:01:39 -04:00
|
|
|
["Members", "CMS Staff", "Misc. Access"], membershipworks_attributes
|
|
|
|
)
|
2020-04-14 01:18:20 -04:00
|
|
|
members = [MembershipworksMember(config, m) for m in memberData]
|
2019-11-04 01:25:17 -05:00
|
|
|
|
2020-03-29 00:15:54 -04:00
|
|
|
formerMemberData = membershipworks.get_members(
|
2020-03-30 14:01:39 -04:00
|
|
|
["Former Members"], membershipworks_attributes
|
|
|
|
)
|
|
|
|
formerMembers = [
|
2020-04-14 01:18:20 -04:00
|
|
|
MembershipworksMember(config, m, formerMember=True) for m in formerMemberData
|
2020-03-30 14:01:39 -04:00
|
|
|
]
|
2020-03-29 00:15:54 -04:00
|
|
|
|
|
|
|
for formerMember in formerMembers:
|
|
|
|
member = next(
|
2020-03-30 14:01:39 -04:00
|
|
|
(
|
|
|
|
m
|
|
|
|
for m in members
|
|
|
|
if m.membershipWorksID == formerMember.membershipWorksID
|
|
|
|
),
|
|
|
|
None,
|
|
|
|
)
|
2020-03-29 00:15:54 -04:00
|
|
|
|
|
|
|
# member exists in another folder
|
|
|
|
if member is not None:
|
|
|
|
member.formerMember = True
|
2020-03-30 14:01:39 -04:00
|
|
|
else: # member is only a former member
|
2020-03-29 00:15:54 -04:00
|
|
|
formerMember.formerMember = True
|
|
|
|
members.append(formerMember)
|
|
|
|
|
2020-04-14 01:18:20 -04:00
|
|
|
for door in config.doors.values():
|
2019-11-04 01:25:17 -05:00
|
|
|
print(door.name, door.ip)
|
|
|
|
update_door(door, members)
|
|
|
|
|
2019-11-08 16:07:26 -05:00
|
|
|
|
2020-03-30 14:01:39 -04:00
|
|
|
if __name__ == "__main__":
|
2019-11-08 16:07:26 -05:00
|
|
|
main()
|