add SeatingService with seating plan generation
This commit is contained in:
parent
96278258ef
commit
b9b5e0ede0
19
config/README.md
Normal file
19
config/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Configuration
|
||||
|
||||
## config.toml
|
||||
|
||||
TBD
|
||||
|
||||
## seating plan
|
||||
|
||||
### creation
|
||||
|
||||
- Create a new seating plan via diagrams.net editor (make sure to use the "editable XML" version)
|
||||
- Boxes with a letter followed by up to 3 numbers are detected as seats
|
||||
- Add the property "category" to designate seats as specific category (otherwise, the default will be applied)
|
||||
|
||||
### exporting
|
||||
|
||||
- Export as SVG when ready
|
||||
- remove the onclick listener from the root element (optional)
|
||||
- add path of svg to seating configuration
|
||||
@ -1,6 +1,7 @@
|
||||
[lan]
|
||||
name="EZ LAN"
|
||||
iteration="0.5"
|
||||
default_category="NORMAL"
|
||||
tickets={ "LUXUS" = 40, "NORMAL" = 10 }
|
||||
prices={ "LUXUS" = 3000, "NORMAL" = 2500 } # Eurocent
|
||||
date_from="2024-10-30 15:00:00"
|
||||
@ -19,3 +20,6 @@
|
||||
sender=""
|
||||
username=""
|
||||
password=""
|
||||
|
||||
[seating]
|
||||
base_svg_path=""
|
||||
|
||||
256
config/seating_plan.example.drawio
Normal file
256
config/seating_plan.example.drawio
Normal file
@ -0,0 +1,256 @@
|
||||
<mxfile host="drawio-plugin" modified="2024-08-20T12:29:20.095Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" etag="_qBa9-8UH2I_if-_58Cs" version="22.1.22" type="embed">
|
||||
<diagram id="23iRSUPoRavnBvh4doch" name="Page-1">
|
||||
<mxGraphModel dx="1746" dy="706" grid="1" gridSize="20" guides="0" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" background="#0A0A0A" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="126" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="20" y="800" as="sourcePoint" />
|
||||
<mxPoint x="800" y="800" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="4" value="<font style="font-size: 28px;"><b>Bürgerhaus Bottenhorn</b></font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontColor=#FFFFFF;" parent="1" vertex="1">
|
||||
<mxGeometry x="820" width="340" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="20" y="20" as="sourcePoint" />
|
||||
<mxPoint x="20" y="800" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="12" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="800" y="800" as="sourcePoint" />
|
||||
<mxPoint x="800" y="20" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="13" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="20" y="20" as="sourcePoint" />
|
||||
<mxPoint x="800" y="20" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="14" value="" style="endArrow=none;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="240" y="800" as="sourcePoint" />
|
||||
<mxPoint x="240" y="560" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="15" value="" style="endArrow=none;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="160" y="560" as="sourcePoint" />
|
||||
<mxPoint x="20" y="560" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="16" value="" style="endArrow=none;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="20" y="520" as="sourcePoint" />
|
||||
<mxPoint x="260" y="520" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="18" value="" style="endArrow=none;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="480" y="720" as="sourcePoint" />
|
||||
<mxPoint x="480" y="520" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="19" value="" style="endArrow=none;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="480" y="720" as="sourcePoint" />
|
||||
<mxPoint x="720" y="720" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="20" value="" style="endArrow=none;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="720" y="720" as="sourcePoint" />
|
||||
<mxPoint x="720" y="520" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="21" value="<font color="#ffffff" style="font-size: 15px;"><b>Schlarfsaal</b></font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontColor=#000000;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="600" width="180" height="140" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="24" value="" style="endArrow=none;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="340" y="520" as="sourcePoint" />
|
||||
<mxPoint x="800" y="520" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25" value="" style="endArrow=none;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="260" y="540" as="sourcePoint" />
|
||||
<mxPoint x="260" y="500" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="27" value="" style="endArrow=none;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="340" y="540" as="sourcePoint" />
|
||||
<mxPoint x="340" y="500" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="28" value="<font style="font-size: 15px;"><b>Einlass<br>und<br>Orga<br></b></font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=default;strokeColor=#FFFFFF;" parent="1" vertex="1">
|
||||
<mxGeometry x="360" y="520" width="120" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="30" value="<font style="font-size: 15px;"><b>Toiletten</b></font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontColor=#FFFFFF;labelBackgroundColor=none;" parent="1" vertex="1">
|
||||
<mxGeometry x="80" y="520" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="31" value="" style="endArrow=classic;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="80" y="540" as="sourcePoint" />
|
||||
<mxPoint x="40" y="540" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="32" value="" style="endArrow=none;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="240" y="560" as="sourcePoint" />
|
||||
<mxPoint x="200" y="560" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="33" value="" style="endArrow=none;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="160" y="580" as="sourcePoint" />
|
||||
<mxPoint x="160" y="540" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="34" value="" style="endArrow=none;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="200" y="580" as="sourcePoint" />
|
||||
<mxPoint x="200" y="540" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="35" value="" style="endArrow=none;dashed=1;html=1;rounded=0;strokeColor=#FFFFFF;" parent="1" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="680" y="520" as="sourcePoint" />
|
||||
<mxPoint x="680" y="20" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="36" value="<font style="font-size: 15px;"><b>Bühne</b></font>" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontColor=#FFFFFF;" parent="1" vertex="1">
|
||||
<mxGeometry x="700" y="260" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="47" value="B01" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="60" y="280" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="49" value="B11" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="120" y="220" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="50" value="B03" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="180" y="280" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="51" value="B10" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="60" y="220" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="52" value="B12" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="180" y="220" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="53" value="B02" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="120" y="280" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="88" value="C01" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="60" y="120" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="89" value="C11" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="120" y="60" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="90" value="C03" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="180" y="120" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="127" value="C10" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="60" y="60" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="92" value="C12" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="180" y="60" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="93" value="C02" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="120" y="120" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="98" value="E13" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="540" y="60" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="99" value="E10" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="360" y="60" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="100" value="E12" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="480" y="60" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="101" value="E11" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="420" y="60" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="104" value="E01" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="360" y="120" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="105" value="E02" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="420" y="120" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="106" value="E03" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="480" y="120" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="107" value="E04" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="540" y="120" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="108" value="D13" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="540" y="220" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="109" value="D10" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="360" y="220" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="110" value="D12" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="480" y="220" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="111" value="D11" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="420" y="220" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="112" value="D01" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="360" y="280" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="113" value="D02" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="420" y="280" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="114" value="D03" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="480" y="280" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="115" value="D04" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="540" y="280" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<object label="A01" category="LUXUS" id="116">
|
||||
<mxCell style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="60" y="440" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
</object>
|
||||
<object label="A11" category="LUXUS" id="117">
|
||||
<mxCell style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="120" y="380" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
</object>
|
||||
<object label="A03" category="LUXUS" id="118">
|
||||
<mxCell style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="180" y="440" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
</object>
|
||||
<object label="A10" category="LUXUS" id="119">
|
||||
<mxCell style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="60" y="380" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
</object>
|
||||
<object label="A12" category="LUXUS" id="120">
|
||||
<mxCell style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="180" y="380" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
</object>
|
||||
<object label="A02" category="LUXUS" id="121">
|
||||
<mxCell style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="120" y="440" width="60" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
</object>
|
||||
<mxCell id="128" value="<font style="font-size: 15px;"><b>Getränke</b></font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=default;" parent="1" vertex="1">
|
||||
<mxGeometry x="500" y="500" width="160" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="125" value="Neben-Ausgang" style="rounded=0;whiteSpace=wrap;html=1;fillColor=default;" parent="1" vertex="1">
|
||||
<mxGeometry x="660" y="760" width="100" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5" value="Eingang" style="rounded=0;whiteSpace=wrap;html=1;fillColor=default;" parent="1" vertex="1">
|
||||
<mxGeometry x="320" y="760" width="80" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
973
config/seating_plan_base.example.svg
Normal file
973
config/seating_plan_base.example.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 89 KiB |
@ -1,28 +1,22 @@
|
||||
import logging
|
||||
from datetime import datetime, date
|
||||
|
||||
from from_root import from_root
|
||||
|
||||
from src.ez_lan_manager.services.AccountingService import AccountingService
|
||||
from src.ez_lan_manager.services.ConfigurationService import ConfigurationService
|
||||
from src.ez_lan_manager.services.DatabaseService import DatabaseService
|
||||
|
||||
from random import randint
|
||||
|
||||
from src.ez_lan_manager.services.MailingService import MailingService
|
||||
from src.ez_lan_manager.services.NewsService import NewsService
|
||||
from src.ez_lan_manager.services.SeatingService import SeatingService
|
||||
from src.ez_lan_manager.services.TicketingService import TicketingService
|
||||
from src.ez_lan_manager.services.UserService import UserService
|
||||
from src.ez_lan_manager.types.News import News
|
||||
from src.ez_lan_manager.types.Transaction import Transaction
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
configuration_service = ConfigurationService(from_root("config.toml"))
|
||||
lan_info = configuration_service.get_lan_info()
|
||||
seating_config = configuration_service.get_seating_configuration()
|
||||
db_config = configuration_service.get_database_configuration()
|
||||
db_service = DatabaseService(db_config)
|
||||
user_service = UserService(db_service)
|
||||
@ -30,21 +24,4 @@ if __name__ == "__main__":
|
||||
news_service = NewsService(db_service)
|
||||
mailing_service = MailingService(configuration_service.get_mailing_service_configuration())
|
||||
ticketing_service = TicketingService(lan_info, db_service, accounting_service)
|
||||
|
||||
print(ticketing_service.refund_ticket(19))
|
||||
#print(ticketing_service.get_available_tickets())
|
||||
|
||||
|
||||
#user_service.create_user("Alex", "alex@gmail.com", "MeinPasswort")
|
||||
#print(user_service.is_login_valid("Alex@gmail.com", "MeinPasswort"))
|
||||
|
||||
# news_service.add_news(News(
|
||||
# news_id=None,
|
||||
# title=f"TITLE{randint(0, 9999)}",
|
||||
# subtitle="",
|
||||
# content="",
|
||||
# author=user_service.get_user(19),
|
||||
# news_date=date(2024, 8, 30)
|
||||
# ))
|
||||
|
||||
|
||||
seating_service = SeatingService(seating_config, lan_info, db_service, ticketing_service)
|
||||
|
||||
@ -4,7 +4,9 @@ from pathlib import Path
|
||||
import logging
|
||||
import tomllib
|
||||
|
||||
from src.ez_lan_manager.types.ConfigurationTypes import DatabaseConfiguration, MailingServiceConfiguration, LanInfo, TicketInfo
|
||||
from from_root import from_root
|
||||
|
||||
from src.ez_lan_manager.types.ConfigurationTypes import DatabaseConfiguration, MailingServiceConfiguration, LanInfo, TicketInfo, SeatingConfiguration
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
@ -50,6 +52,7 @@ class ConfigurationService:
|
||||
try:
|
||||
lan_info = self._config["lan"]
|
||||
ticket_info = TicketInfo(
|
||||
default_category=lan_info["default_category"],
|
||||
categories=list(lan_info["tickets"].keys()),
|
||||
_prices=lan_info["prices"],
|
||||
_available_tickets=lan_info["tickets"]
|
||||
@ -64,3 +67,17 @@ class ConfigurationService:
|
||||
except KeyError:
|
||||
logger.fatal("Error loading LAN Info, exiting...")
|
||||
sys.exit(1)
|
||||
|
||||
def get_seating_configuration(self) -> SeatingConfiguration:
|
||||
try:
|
||||
seating_config = self._config["seating"]
|
||||
base_svg_file_path = from_root(seating_config["base_svg_path"])
|
||||
if not base_svg_file_path.exists():
|
||||
logger.fatal(f"Specified seating plan SVG file was not found at {base_svg_file_path}! Exiting...")
|
||||
sys.exit(1)
|
||||
return SeatingConfiguration(
|
||||
base_svg_path=base_svg_file_path
|
||||
)
|
||||
except KeyError:
|
||||
logger.fatal("Error loading seating configuration, exiting...")
|
||||
sys.exit(1)
|
||||
|
||||
@ -8,6 +8,7 @@ from mariadb import Cursor
|
||||
|
||||
from src.ez_lan_manager.types.ConfigurationTypes import DatabaseConfiguration
|
||||
from src.ez_lan_manager.types.News import News
|
||||
from src.ez_lan_manager.types.Seat import Seat
|
||||
from src.ez_lan_manager.types.Ticket import Ticket
|
||||
from src.ez_lan_manager.types.Transaction import Transaction
|
||||
from src.ez_lan_manager.types.User import User
|
||||
@ -251,3 +252,45 @@ class DatabaseService:
|
||||
logger.warning(f"Error deleting ticket: {e}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def generate_fresh_seats_table(self, seats: list[tuple[str, str]]) -> None:
|
||||
""" WARNING: THIS WILL DELETE ALL EXISTING DATA! DO NOT USE ON PRODUCTION DATABASE! """
|
||||
cursor = self._get_cursor()
|
||||
try:
|
||||
cursor.execute("TRUNCATE seats;")
|
||||
for seat in seats:
|
||||
cursor.execute("INSERT INTO seats (seat_id, seat_category) VALUES (?, ?);", (seat[0], seat[1]))
|
||||
self._connection.commit()
|
||||
except Exception as e:
|
||||
logger.warning(f"Error generating fresh seats table: {e}")
|
||||
return
|
||||
|
||||
def get_seating_info(self) -> list[Seat]:
|
||||
results = []
|
||||
cursor = self._get_cursor()
|
||||
try:
|
||||
cursor.execute("SELECT seats.*, users.* FROM seats LEFT JOIN users ON seats.user = users.user_id;")
|
||||
except Exception as e:
|
||||
logger.warning(f"Error getting seats table: {e}")
|
||||
return results
|
||||
|
||||
|
||||
for seat_raw in cursor.fetchall():
|
||||
if seat_raw[3] is None: # Empty seat
|
||||
results.append(Seat(seat_raw[0], bool(seat_raw[1]), seat_raw[2], None))
|
||||
else:
|
||||
user = self._map_db_result_to_user(seat_raw[4:])
|
||||
results.append(Seat(seat_raw[0], bool(seat_raw[1]), seat_raw[2], user))
|
||||
|
||||
return results
|
||||
|
||||
def seat_user(self, seat_id: str, user_id: int) -> bool:
|
||||
cursor = self._get_cursor()
|
||||
try:
|
||||
cursor.execute("UPDATE seats SET user = ? WHERE seat_id = ?;", (user_id, seat_id))
|
||||
affected_rows = cursor.rowcount
|
||||
self._connection.commit()
|
||||
except Exception as e:
|
||||
logger.warning(f"Error seating user: {e}")
|
||||
return False
|
||||
return bool(affected_rows)
|
||||
|
||||
152
src/ez_lan_manager/services/SeatingService.py
Normal file
152
src/ez_lan_manager/services/SeatingService.py
Normal file
@ -0,0 +1,152 @@
|
||||
import logging
|
||||
import re
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from from_root import from_root
|
||||
|
||||
from src.ez_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ez_lan_manager.services.TicketingService import TicketingService
|
||||
from src.ez_lan_manager.types.ConfigurationTypes import LanInfo, SeatingConfiguration
|
||||
from src.ez_lan_manager.types.Seat import Seat
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
class NoTicketError(Exception):
|
||||
pass
|
||||
|
||||
class SeatNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
class WrongCategoryError(Exception):
|
||||
pass
|
||||
|
||||
class SeatAlreadyTakenError(Exception):
|
||||
pass
|
||||
|
||||
class SeatingService:
|
||||
def __init__(self, seating_configuration: SeatingConfiguration, lan_info: LanInfo, db_service: DatabaseService, ticketing_service: TicketingService) -> None:
|
||||
self._seating_configuration = seating_configuration
|
||||
self._lan_info = lan_info
|
||||
self._db_service = db_service
|
||||
self._ticketing_service = ticketing_service
|
||||
self._seating_plan = StringIO()
|
||||
ElementTree.parse(self._seating_configuration.base_svg_path).write(self._seating_plan, encoding="unicode")
|
||||
|
||||
|
||||
def get_seating(self) -> list[Seat]:
|
||||
return self._db_service.get_seating_info()
|
||||
|
||||
def get_seat(self, seat_id: str, cached_data: Optional[list[Seat]] = None) -> Optional[Seat]:
|
||||
all_seats = self.get_seating() if not cached_data else cached_data
|
||||
for seat in all_seats:
|
||||
if seat.seat_id == seat_id:
|
||||
return seat
|
||||
|
||||
def seat_user(self, user_id: int, seat_id: str) -> None:
|
||||
user_ticket = self._ticketing_service.get_user_ticket(user_id)
|
||||
if not user_ticket:
|
||||
raise NoTicketError
|
||||
|
||||
seat = self.get_seat(seat_id)
|
||||
if not seat:
|
||||
raise SeatNotFoundError
|
||||
|
||||
if seat.category != user_ticket.category:
|
||||
raise WrongCategoryError
|
||||
|
||||
if seat.user is not None:
|
||||
raise SeatAlreadyTakenError
|
||||
|
||||
self._db_service.seat_user(seat_id, user_id)
|
||||
self.update_svg_with_seating_status()
|
||||
|
||||
def generate_new_seating_table(self, seating_plan_fp: Path, no_confirm: bool = False) -> None:
|
||||
if not no_confirm:
|
||||
confirm = input("WARNING: THIS ACTION WILL DELETE ALL SEATING DATA! TYPE 'AGREE' TO CONTINUE: ")
|
||||
if confirm != "AGREE":
|
||||
logging.info("Seating table generation aborted...")
|
||||
return
|
||||
|
||||
et = ElementTree.parse(seating_plan_fp)
|
||||
seat_ids = []
|
||||
for child in et.getroot().findall(".//mxCell"):
|
||||
possible_seat_identifier = child.get("value")
|
||||
try:
|
||||
if re.match(r"^\w\d{1,3}$", possible_seat_identifier):
|
||||
seat_ids.append((possible_seat_identifier, self._lan_info.ticket_info.default_category))
|
||||
except TypeError:
|
||||
continue
|
||||
|
||||
for child in et.getroot().findall(".//object"):
|
||||
possible_seat_identifier = child.get("label")
|
||||
try:
|
||||
if re.match(r"^\w\d{1,3}$", possible_seat_identifier):
|
||||
category = child.get("category")
|
||||
seat_ids.append((possible_seat_identifier, category))
|
||||
except TypeError:
|
||||
continue
|
||||
|
||||
self._db_service.generate_fresh_seats_table(sorted(seat_ids, key=lambda sd: sd[0]))
|
||||
self.update_svg_with_seating_status()
|
||||
|
||||
def update_svg_with_seating_status(self) -> None:
|
||||
et = ElementTree.parse(self._seating_configuration.base_svg_path)
|
||||
root = et.getroot()
|
||||
namespace = {'svg': root.tag.split('}')[0].strip('{')} if '}' in root.tag else {}
|
||||
rect_g_pairs = []
|
||||
last_rect = None
|
||||
|
||||
for elem in root.iter():
|
||||
if elem.tag == f"{{{namespace.get('svg')}}}rect":
|
||||
last_rect = elem
|
||||
elif elem.tag == f"{{{namespace.get('svg')}}}g":
|
||||
if last_rect is not None:
|
||||
rect_g_pairs.append((last_rect, elem))
|
||||
last_rect = None
|
||||
|
||||
all_seats = self.get_seating()
|
||||
|
||||
for rect, g in rect_g_pairs:
|
||||
seat_id = self.get_seat_id_from_element(g, namespace)
|
||||
if not seat_id:
|
||||
continue
|
||||
seat = self.get_seat(seat_id, cached_data=all_seats)
|
||||
if not seat.is_blocked and seat.user is None:
|
||||
rect.set("fill", "rgb(102, 255, 51)")
|
||||
elif not seat.is_blocked and seat.user is not None:
|
||||
rect.set("fill", "rgb(204, 0, 0)")
|
||||
else:
|
||||
rect.set("fill", "rgb(190,190,190)")
|
||||
# @ToDo: Set URL's properly
|
||||
rect.set('onclick', f"window.open('https://httpbin.org/get?seat_id={seat_id}', '_blank')")
|
||||
g.set('onclick', f"window.open('https://httpbin.org/get?seat_id={seat_id}', '_blank')")
|
||||
|
||||
# Debug output
|
||||
et.write(from_root("debug_seating_plan.svg"))
|
||||
|
||||
self._seating_plan = StringIO()
|
||||
et.write(self._seating_plan, encoding='unicode')
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_seat_id_from_element(element: ElementTree.Element, namespace: dict) -> Optional[str]:
|
||||
seat_id = None
|
||||
for child in element.iter():
|
||||
if child.tag == f"{{{namespace.get('svg')}}}text":
|
||||
# Extract identifier from <text> element
|
||||
seat_id = child.text.strip() if child.text else None
|
||||
elif child.tag.endswith('div') and child.text:
|
||||
# Extract identifier from <foreignObject>/<div>
|
||||
seat_id = child.text.strip()
|
||||
|
||||
if seat_id: # Break if we've already found the identifier
|
||||
break
|
||||
try:
|
||||
if re.match(r"^\w\d{1,3}$", seat_id):
|
||||
return seat_id
|
||||
except TypeError:
|
||||
pass
|
||||
return
|
||||
@ -1,6 +1,8 @@
|
||||
from copy import copy
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class NoSuchCategoryError(Exception):
|
||||
pass
|
||||
@ -15,6 +17,7 @@ class DatabaseConfiguration:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TicketInfo:
|
||||
default_category: str
|
||||
categories: list[str]
|
||||
_prices: dict[str, int]
|
||||
_available_tickets: dict[str, int]
|
||||
@ -50,3 +53,7 @@ class LanInfo:
|
||||
ticket_info: TicketInfo
|
||||
date_from: datetime
|
||||
date_till: datetime
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SeatingConfiguration:
|
||||
base_svg_path: Path
|
||||
|
||||
12
src/ez_lan_manager/types/Seat.py
Normal file
12
src/ez_lan_manager/types/Seat.py
Normal file
@ -0,0 +1,12 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Seat:
|
||||
seat_id: str
|
||||
is_blocked: bool
|
||||
category: str
|
||||
user: Optional[User]
|
||||
Loading…
Reference in New Issue
Block a user