218 lines
8.6 KiB
Python
Executable File
218 lines
8.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
from common import doors, getMembershipworksData, memberLevels
|
|
from hid.Credential import Credential
|
|
from hid.DoorController import ROOT, E
|
|
|
|
|
|
class Member():
|
|
def __init__(self, forename, surname, membershipWorksID,
|
|
middleName="", email="", phone="",
|
|
cardholderID=None, doorAccess=[], onHold=False,
|
|
credentials=set(), levels=[], schedules=[]):
|
|
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.onHold = onHold
|
|
self.credentials = credentials
|
|
self.levels = levels
|
|
self.schedules = schedules
|
|
|
|
def __repr__(self):
|
|
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}
|
|
On Hold? {self.onHold}
|
|
Credentials: {self.credentials}
|
|
Levels: {self.levels}
|
|
Schedules: {self.schedules}
|
|
"""
|
|
|
|
@classmethod
|
|
def from_cardholder(cls, data):
|
|
# TODO: maybe keep all attribs, and just add them in
|
|
# from_MembershipWorks?
|
|
credentials = set(
|
|
Credential(hex=(c.attrib['rawCardNumber']))
|
|
for c in data.findall('{*}Credential'))
|
|
roles = [r.attrib['scheduleName'] for r in data.findall('{*}Role')]
|
|
levels = data.attrib.get('custom1', "").split('|')
|
|
return cls(
|
|
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'],
|
|
credentials=credentials,
|
|
levels=levels,
|
|
schedules=roles)
|
|
|
|
@classmethod
|
|
def from_MembershipWorks(cls, data):
|
|
if data["Access Card Number"] != "":
|
|
credentials = set([Credential(
|
|
code=(data["Access Card Facility Code"],
|
|
data["Access Card Number"]))])
|
|
else:
|
|
credentials = set()
|
|
|
|
levels = {k: v for k, v in memberLevels.items() if data[k] == k}
|
|
doorAccess = [door for door, doorData in doors.items()
|
|
if data["Access " + doorData.access + "?"] == "Y"]
|
|
|
|
return cls(data["First Name"],
|
|
data["Last Name"],
|
|
membershipWorksID=data["Account ID"],
|
|
email=data["Email"],
|
|
phone=data["Phone"],
|
|
doorAccess=doorAccess,
|
|
onHold=data["Account on Hold"] != "",
|
|
credentials=credentials,
|
|
levels=list(levels.keys()),
|
|
schedules=list(levels.values()))
|
|
|
|
def attribs(self):
|
|
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}
|
|
|
|
def schedulesForDoor(self, door):
|
|
if door.name not in self.doorAccess or self.onHold:
|
|
return []
|
|
else:
|
|
return self.schedules
|
|
|
|
def make_schedules(self, door, schedulesMap):
|
|
roles = [
|
|
E.Role({"roleID": self.cardholderID,
|
|
"scheduleID": schedulesMap[schedule],
|
|
"resourceID": "0"})
|
|
for schedule in self.schedulesForDoor(door)]
|
|
|
|
return E.RoleSet({"action": "UD", "roleSetID": self.cardholderID},
|
|
E.Roles(*roles))
|
|
|
|
def make_credentials(self, newCredentials, cardFormats):
|
|
out = [
|
|
E.Credential(
|
|
{"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]
|
|
|
|
return E.Credentials({"action": "AD"}, *out)
|
|
|
|
|
|
def update_door(door, members):
|
|
cardFormats = door.get_cardFormats()
|
|
cardholders = {member.membershipWorksID: member
|
|
for member in [Member.from_cardholder(ch)
|
|
for ch in door.get_cardholders()]}
|
|
schedulesMap = door.get_scheduleMap()
|
|
allCredentials = set(Credential(hex=c.attrib['rawCardNumber'])
|
|
for c in door.get_credentials())
|
|
|
|
# TODO: can I combine requests?
|
|
for member in members:
|
|
# cardholder exists, compare contents
|
|
if member.membershipWorksID in cardholders:
|
|
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()}")
|
|
door.doXMLRequest(ROOT(
|
|
E.Cardholders(
|
|
{"action": "UD", "cardholderID": member.cardholderID},
|
|
E.CardHolder(member.attribs()))))
|
|
|
|
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(card
|
|
for m in members if m != member
|
|
for card in m.credentials)
|
|
|
|
# cards removed, and won't be reassigned to someone else
|
|
for card in (oldCards - newCards) - allNewCards:
|
|
door.doXMLRequest(ROOT(E.Credentials(
|
|
{"action": "UD",
|
|
"rawCardNumber": card.hex,
|
|
"isCard": "true"},
|
|
E.Credential({"cardholderID": ""}))))
|
|
|
|
if newCards - oldCards: # cards added
|
|
for card in newCards & allNewCards: # new card exists in another member
|
|
raise Exception(f"Duplicate Card in input data! {card}")
|
|
|
|
# card existed in door, and needs to be reassigned
|
|
for card in newCards & allCredentials:
|
|
door.doXMLRequest(ROOT(E.Credentials(
|
|
{"action": "UD",
|
|
"rawCardNumber": card.hex,
|
|
"isCard": "true"},
|
|
E.Credential({"cardholderID": member.cardholderID}))))
|
|
|
|
# cards that never existed, and need to be created
|
|
if newCards - allCredentials:
|
|
door.doXMLRequest(ROOT(member.make_credentials(
|
|
newCards - allCredentials, cardFormats)))
|
|
|
|
if member.schedulesForDoor(door) != ch.schedules:
|
|
print("- Updating schedule for" +
|
|
f" {member.forename} {member.surname}:" +
|
|
f" {ch.schedules} -> {member.schedulesForDoor(door)}")
|
|
door.doXMLRequest(ROOT(
|
|
member.make_schedules(door, schedulesMap)))
|
|
else: # cardholder did not exist, so add them
|
|
print(f"- Adding Member:")
|
|
print(member)
|
|
resp = door.doXMLRequest(ROOT(
|
|
E.Cardholders(
|
|
{"action": "AD"},
|
|
E.Cardholder(member.attribs()))))
|
|
member.cardholderID = resp.find('{*}Cardholders/{*}Cardholder') \
|
|
.attrib["cardholderID"]
|
|
door.doXMLRequest(ROOT(
|
|
member.make_credentials(member.credentials, cardFormats),
|
|
member.make_schedules(door, schedulesMap)))
|
|
|
|
# TODO: delete cardholders that are no longer members?
|
|
|
|
|
|
def main():
|
|
memberData = getMembershipworksData(
|
|
['members', 'staff', 'misc'],
|
|
"_id,nam,phn,eml,lvl,lbl,xws,xms,xsc,xas,xfd,xac,xcf")
|
|
|
|
members = [Member.from_MembershipWorks(m) for m in memberData]
|
|
|
|
for door in doors.values():
|
|
print(door.name, door.ip)
|
|
update_door(door, members)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|