Madiwka3 vor 1 Jahr
Ursprung
Commit
7ad143825c
7 geänderte Dateien mit 543 neuen und 5 gelöschten Zeilen
  1. +15
    -1
      app/apis/v1/route_report.py
  2. +310
    -1
      app/db/repository/report.py
  3. +74
    -0
      app/report.pdf
  4. +137
    -0
      app/report1.pdf
  5. +3
    -1
      app/requirements.txt
  6. +1
    -1
      docker-compose.yml
  7. +3
    -1
      requirements.txt

+ 15
- 1
app/apis/v1/route_report.py Datei anzeigen

@@ -1,8 +1,9 @@
from fastapi import Depends, APIRouter, HTTPException, status
from fastapi.responses import StreamingResponse
from requests import Session
from apis.v1.route_auth import get_current_user
from db.models.user import User
from db.repository.report import get_repot_jobsdone_by_driver
from db.repository.report import get_repot_jobsdone_by_driver, get_pdf

from db.session import get_db

@@ -20,3 +21,16 @@ def get_report_jobsdone(driver_id: int, db: Session = Depends(get_db), current_u
status_code=404, detail=f"Driver with id {driver_id} not found"
)
return report

@router.get("/pdf/{driver_id}", status_code=status.HTTP_200_OK)
def get_report_pdf(driver_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
if current_user.Role != "Admin":
raise HTTPException(
status_code=403, detail="You are not authorized to perform this action"
)
report = get_pdf(driver_id, db)
if report == "notdriver":
raise HTTPException(
status_code=404, detail=f"Driver with id {driver_id} not found"
)
return StreamingResponse(open(report, "rb"), media_type="application/pdf")

+ 310
- 1
app/db/repository/report.py Datei anzeigen

@@ -1,4 +1,5 @@
from datetime import datetime
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
from db.models.drivetask import DriveTask

@@ -7,6 +8,18 @@ from db.models.fuelingtask import FuelingTask
from db.models.maintenancejob import MaintenanceJob
from db.repository.maintenancejob import calculate_total_cost

from reportlab.lib import colors
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Image, PageBreak
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.linecharts import HorizontalLineChart
from reportlab.graphics.widgets.markers import makeMarker
from reportlab.platypus import Spacer
from reportlab.graphics.charts.axes import CategoryAxis
from dateutil import parser


def get_repot_jobsdone_by_driver(driver_id: int, db: Session):
driver = db.query(User).filter(User.Id == driver_id).first()
@@ -75,4 +88,300 @@ def get_repot_jobsdone_by_driver(driver_id: int, db: Session):
maintenspent.append(maintenpoint)
res["MaintenanceSpent"] = maintenspent
res["TotalMaintenanceSpent"] = totalmaintenspent
return res
return res

def get_pdf(driver_id: int, db: Session):
report_data = get_repot_jobsdone_by_driver(driver_id, db)
if report_data == "notdriver":
return "notdriver"

# Assuming your report data is stored in a variable named 'report_data'

# Create a PDF document
pdf_filename = "report1.pdf"
document = SimpleDocTemplate(pdf_filename, pagesize=letter)

# Create a list to hold the contents of the PDF
content = []

# Add a title to the PDF
title_style = getSampleStyleSheet()['Title']
title = Paragraph("Driver Report", title_style)
content.append(title)
content.append(Paragraph("<br/>", title_style))
print(report_data["Driver"])

# Add detailed information about the driver
driver_info = [
["Driver Name", report_data["Driver"]["Name"]],
["Last Name", report_data["Driver"]["LastName"]],
["Government ID", report_data["Driver"]["GovernmentId"]],
["Email", report_data["Driver"]["Email"]],
["Contact Number", report_data["Driver"]["ContactNumber"]],
["Address", report_data["Driver"]["Address"]],
["Role", report_data["Driver"]["Role"]],
]

driver_table_style = TableStyle([
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('BOX', (0, 0), (-1, -1), 1, colors.black),
])

driver_table = Table(driver_info, style=driver_table_style)
content.append(Paragraph("<br/><br/>", title_style))

vehicle = report_data["Driver"]["AssignedVehicle"].__dict__
print(vehicle)
loc = vehicle["CurrentLocation"][0]
if len(vehicle["CurrentLocation"]) > 1:
loc += ", " + vehicle["CurrentLocation"][1]
# Add vehicle details
vehicle_info = [
["Vehicle Type", vehicle["Type"]],
["Vehicle Model", vehicle["Model"]],
["Year", vehicle["Year"]],
["License Plate", vehicle["LicensePlate"]],
["Current Location", loc],
["Mileage", vehicle["Mileage"]],
["Capacity", vehicle["Capacity"]],
]


vehicle_table_style = TableStyle([
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('BOX', (0, 0), (-1, -1), 1, colors.black),
])

vehicle_table = Table(vehicle_info, style=vehicle_table_style)
content.append(Spacer(1, 12)) # Add space between the tables
content.append(Table([[driver_table, vehicle_table]]))
content.append(Paragraph("<br/><br/>", title_style))
total_info = [
["Total Tasks Done", report_data["tasks"].__len__()],
["Total Fuel Spent", report_data["TotalFuelSpent"]],
["Total Time Spent", report_data["TotalTime"]],
["Total Distance Driven", report_data["TotalDistance"]],
["Total Money Spent", report_data["TotalMaintenanceSpent"]],
]

total_table_style = TableStyle([
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('BOX', (0, 0), (-1, -1), 1, colors.black),
])

total_table = Table(total_info, style=total_table_style)
content.append(total_table)
content.append(Paragraph("<br/><br/>", title_style))

content.append(PageBreak())





title_style = getSampleStyleSheet()["Heading1"]
content.append(Paragraph("Driving Tasks", title_style))

title_style = getSampleStyleSheet()["Heading2"]
content.append(Paragraph("Distance Driven over Time", title_style))
tasks_data = report_data["tasks"]
if tasks_data.__len__() == 0:
content.append(Paragraph("No tasks done.", title_style))
else:
task_values = [entry["DistanceAtTime"] for entry in tasks_data]
task_dates = [parser.parse(entry['EndDateTime']).strftime("%m/%d/%Y, %H:%M:%S") for entry in tasks_data]

drawing = Drawing(width=400, height=200)
chart = HorizontalLineChart()
chart.x = 0
chart.y = 50
chart.width = 500
chart.height = 125
chart.data = [task_values]
chart.categoryAxis.labels.boxAnchor = 'e'
chart.valueAxis.valueMin = 0
chart.valueAxis.valueMax = max(task_values) + 10
chart.lines[0].strokeColor = colors.blue
chart.lines[0].strokeWidth = 2
chart.categoryAxis.categoryNames = [str(date) for date in task_dates]
chart.categoryAxis.labels.angle = 90
drawing.add(chart)

# Add space between the tables
content.append(drawing)
content.append(Paragraph("<br/><br/>", title_style))
content.append(Paragraph("<br/><br/>", title_style))
content.append(Paragraph("<br/><br/>", title_style))


# Add tasks information
title_style = getSampleStyleSheet()["Heading2"]
content.append(Paragraph("All DriveTasks", title_style))
tasks_header = ["Task ID", "Description", "Start Location", "End Location", "Distance Covered", "Status", "Start DateTime", "End DateTime"]
tasks_info = []

for task in tasks_data:
task_info = [f"{task['Id']}", task["Description"], f"{task['StartLocation'][0]}, {task['StartLocation'][1]}", f"{task['EndLocation'][0]}, {task['EndLocation'][1]}",
"{} km".format(task["DistanceCovered"]), task["Status"], task['StartDateTime'].strftime("%m/%d/%Y, %H:%M:%S"), parser.parse(task['EndDateTime']).strftime("%m/%d/%Y, %H:%M:%S")]
tasks_info.append(task_info)
# Define the styles
styles = getSampleStyleSheet()
normal_style = styles["Normal"]

# Convert strings to Paragraphs to allow line breaks
for row in tasks_info:
for i, cell in enumerate(row):
row[i] = Paragraph(cell, normal_style)
tasks_table_style = TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('GRID', (0, 0), (-1, -1), 1, colors.black),
])

tasks_table = Table([tasks_header] + tasks_info, style=tasks_table_style)
content.append(tasks_table)
content.append(PageBreak())


title_style = getSampleStyleSheet()["Heading1"]
content.append(Paragraph("Fuel Spent Over Time", title_style))
# Add chart of fuel spendings
fuel_spent_data = report_data["FuelSpent"]
if fuel_spent_data.__len__() == 0:
content.append(Paragraph("No fuel spent.", title_style))
else:
fuel_values = [entry["total"] for entry in fuel_spent_data]
fuel_dates = [entry["date"] for entry in fuel_spent_data]

drawing = Drawing(width=400, height=200)
chart = HorizontalLineChart()
chart.x = 0
chart.y = 50
chart.width = 500
chart.height = 125
chart.data = [fuel_values]
chart.categoryAxis.labels.boxAnchor = 'e'
chart.valueAxis.valueMin = 0
chart.valueAxis.valueMax = max(fuel_values) + 10
chart.lines[0].strokeColor = colors.blue
chart.lines[0].strokeWidth = 2
chart.categoryAxis.categoryNames = [str(date) for date in fuel_dates]
chart.categoryAxis.labels.angle = 90
drawing.add(chart)

# Add space between the tables
content.append(drawing)
content.append(Paragraph("<br/><br/>", title_style))
fuel_header = ["Date", "Fuel Refilled", "Total Fuel Spent"]
fuel_info = []

for fuel in fuel_spent_data:
fuel_info1 = [fuel["date"].strftime("%m/%d/%Y, %H:%M:%S"), "{} L".format(fuel["fuelrefilled"]), "{} L".format(fuel["total"])]
fuel_info.append(fuel_info1)
# Define the styles
styles = getSampleStyleSheet()
normal_style = styles["Normal"]

# Convert strings to Paragraphs to allow line breaks
for row in fuel_info:
for i, cell in enumerate(row):
row[i] = Paragraph(cell, normal_style)
fuel_table_style = TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('GRID', (0, 0), (-1, -1), 1, colors.black),
])

fuel_table = Table([fuel_header] + fuel_info, style=fuel_table_style)
content.append(fuel_table)
content.append(PageBreak())


title_style = getSampleStyleSheet()["Heading1"]
content.append(Paragraph("Maintenance Over Time", title_style))
# Add chart of fuel spendings
maintenance_data = report_data["MaintenanceSpent"]
if maintenance_data.__len__() == 0:
content.append(Paragraph("No maintenance done.", title_style))
else:
maintenance_values = [entry["total"] for entry in maintenance_data]
maintenance_dates = [entry["date"] for entry in maintenance_data]

drawing = Drawing(width=400, height=200)
chart = HorizontalLineChart()
chart.x = 0
chart.y = 50
chart.width = 500
chart.height = 125
chart.data = [maintenance_values]
chart.categoryAxis.labels.boxAnchor = 'e'
chart.valueAxis.valueMin = 0
chart.valueAxis.valueMax = max(maintenance_values) + 10
chart.lines[0].strokeColor = colors.blue
chart.lines[0].strokeWidth = 2
chart.categoryAxis.categoryNames = [str(date) for date in maintenance_dates]
chart.categoryAxis.labels.angle = 90
drawing.add(chart)

# Add space between the tables
content.append(drawing)
content.append(Paragraph("<br/><br/>", title_style))
maintenance_header = ["Date", "Maintenance Cost", "Total Maintenance Cost"]
maintenance_info = []

for fuel in maintenance_data:
fuel_info1 = [fuel["date"].strftime("%m/%d/%Y, %H:%M:%S"), "{} L".format(fuel["cost"]), "{} L".format(fuel["total"])]
maintenance_info.append(fuel_info1)
# Define the styles
styles = getSampleStyleSheet()
normal_style = styles["Normal"]

# Convert strings to Paragraphs to allow line breaks
for row in maintenance_info:
for i, cell in enumerate(row):
row[i] = Paragraph(cell, normal_style)
maintenance_table_style = TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('GRID', (0, 0), (-1, -1), 1, colors.black),
])

maintenance_table = Table([maintenance_header] + maintenance_info, style=maintenance_table_style)
content.append(maintenance_table)
document.build(content)

print(f"PDF generated successfully: {pdf_filename}")
return pdf_filename

+ 74
- 0
app/report.pdf Datei anzeigen

@@ -0,0 +1,74 @@
%PDF-1.4
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
1 0 obj
<<
/F1 2 0 R /F2 3 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/Contents 8 0 R /MediaBox [ 0 0 612 792 ] /Parent 7 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<

>>
/Type /Page
>>
endobj
5 0 obj
<<
/PageMode /UseNone /Pages 7 0 R /Type /Catalog
>>
endobj
6 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20231118170352-06'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20231118170352-06'00') /Producer (ReportLab PDF Library - www.reportlab.com)
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
7 0 obj
<<
/Count 1 /Kids [ 4 0 R ] /Type /Pages
>>
endobj
8 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 615
>>
stream
Gas1\bAQ&g&A7<Zp<Z/F^atgjqAA8o'Q&Dsq>haJ=p:4^b&[CV>tUhb)-0")>`Eo=G.BF2VhN%[\H02hiQF@c$7K6^'iRqXK=eGa;rEmS*>gFXR4pg4h62ED3%GT=+Vb%AU5BLZ""e0.e(=-oY&:lJDs6+.]^NK31R)fS?Ge*c+j3AZ93Z&'m5\q:8&^:h(i"m</If[(4bP_8<?GOThek`!+ZG2rWYb&gbH2OR#\fMp[9Y=R:<!Z+*4d5/::@,[Lgh/(d93?S"tfePatuV,&C,K7YU,%u5SZ';@3[=F!FDr;jpedp_7ViSXO;r7`CutEKei&U0b-RpcAejLR[&Ju"<%V)S4<@MC-m)W=Z.2CZs0D?RJHL#a0cToSf%jg%m&gdYBFkh+8W!c??=]=N73W8S9)]DWH[<?R34*Q()1?jA\He6eUqpd<;sQqFpCiU0PQUHRZ1,p%P`%85DC3VHk1)/ed7n@eVPP_[+j,ZkZ05CkY1u&<O[Bij%4[_@MH.`@X\qHAtC<g/,PT\Dpue!*pD]m\s#k"@MD`k"3aOh?\&'=21N@d5dtlj.8I*J-^`lr965\6:,)XWB:.Zj7Npg]U?rXi[ZA=Xi]iN9+S#~>endstream
endobj
xref
0 9
0000000000 65535 f
0000000073 00000 n
0000000114 00000 n
0000000221 00000 n
0000000333 00000 n
0000000526 00000 n
0000000594 00000 n
0000000877 00000 n
0000000936 00000 n
trailer
<<
/ID
[<5763d16e9cd2e7059c1e01e0f37189ef><5763d16e9cd2e7059c1e01e0f37189ef>]
% ReportLab generated PDF document -- digest (http://www.reportlab.com)

/Info 6 0 R
/Root 5 0 R
/Size 9
>>
startxref
1641
%%EOF

+ 137
- 0
app/report1.pdf Datei anzeigen

@@ -0,0 +1,137 @@
%PDF-1.4
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 5 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/Contents 12 0 R /MediaBox [ 0 0 612 792 ] /Parent 11 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<

>>
/Type /Page
>>
endobj
5 0 obj
<<
/BaseFont /Times-Roman /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/Contents 13 0 R /MediaBox [ 0 0 612 792 ] /Parent 11 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<

>>
/Type /Page
>>
endobj
7 0 obj
<<
/Contents 14 0 R /MediaBox [ 0 0 612 792 ] /Parent 11 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<

>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 15 0 R /MediaBox [ 0 0 612 792 ] /Parent 11 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<

>>
/Type /Page
>>
endobj
9 0 obj
<<
/PageMode /UseNone /Pages 11 0 R /Type /Catalog
>>
endobj
10 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20231118192517-06'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20231118192517-06'00') /Producer (ReportLab PDF Library - www.reportlab.com)
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
11 0 obj
<<
/Count 4 /Kids [ 4 0 R 6 0 R 7 0 R 8 0 R ] /Type /Pages
>>
endobj
12 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 880
>>
stream
GauHI?#u2p'Re<2\<#o'mKX;J;j4YbVJ9>41\PZ3CJ"ag!i_B5mq#X86F%0CdFehWbIM9SSiq:k5]BPJNAH*/&\L<*5T)uIJ\$jLd/qUfaj18NaCtX'a:O:bU1;/WJn_T%_OM$;(snpf@BAUa;rY;1^5\HJCgJBJZ@H`$<^l[$U'HK\^oL`H:3hP'J(h]AVsR!bh\<Cu"7th&(^h7nVM;";g;9PTQo#Xc6^Mf"!),R-^^^3/27/0-MoZARoRWX!Z*PVr.-:,b>4"M19X`+26UTi94nN`e&##me$W0r7KEg0"WCIofFK/ac$;l'qo*X@##?VG*0ZNEED7!WJEG(\1dK)B2>O!Ei1k/9%1)m>g21DFn9#N-o-MqBg9Cj*M=VXNBC9BI0f'*-W\L_8o%WE?E0O#e$[7Z4_a?.,r[I6<;n%mZDq>qaf:%C=WgEa\f$lko(LD4Xu$h=uYHU4q>lMq/(=2!I!`-%sFhmiYdhC!:m+kj)9&l8goV&"P*QQdNVIEQo8QI=nF9ks0[g8[c\9GK$O`CL5]Q,;Z/c"UQB%,$I$(J-1L;Snc"<.esFplBWPZJ3";P`OLH;\XA0/lXC%6s8Sc\4m=j%co=lK<poSBcNbU%ZGMHAWfZj)I.eYHCj;]iD,bs[>:#]4",kR\pr-7bk2sgl$%^cgH2)2*)O#5d-lee]A^'qo&6=:kAUsAHeN,tR$f2kI0u)bJ!;Wg\RcGZpDg@ue&Q:\E1Utnb-XVW%W\!XcAC?p`hK?],a0VFRN,jlHaP:pE.,CDP'g2"bG!`0\s[6'Pa0="$2WGboT5Kc,Z6r5QJq4m?_Z-+;rf[XanBBQllKoB1@"B*G0fJ8fXH/3C1$_@O4]qcIq`hgr!#XFAF]~>endstream
endobj
13 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1144
>>
stream
GauHL966RV&AJ$Cka5ssXORE0gtFkOmGu]M>Gh^e9eoKA88U<aqd#m#?iq_amW]/gCA6K5lB><M6Lb<VFg`LTF%Rd'5_,!hOar5j8ajc<3r1u@Vl;M0'8FXXUUb4<1dWu(88,=r'Lk>,'ZLR$`Q`Sr[TGbVFLlhPI3@c18M:PDGPkIgj,W=IO&9-R`GRnGcSAtbA8AJrbI/[%XKj`[6H7Ri<3kF%s7h3:^Y&@:s6%eU?RVCFJstJ7!]5si\NJD8O$*%I["Yp-X/'9[Qb9RK%gV]S*t7:73fm8U9KF-Ak\TA?ZiLE4L4Fi69co`ZWk%U^l)u>[=N;"'N,q$c!)0>[0Zs/\1qjUpMT[7,KK-L<IrPa;&4]*NqCXS&'=mR^16r+*:7EXD.ER7OL8!5L4@p_g73\jZk]J`f-6ae%Yi_Y2fj#d7V+K[jIBTufifT+dp[3A[$P7QGkBIid8P_;!GWM-lAf.EW]JA4+2_=4?IRUm+^kJK\`[C\1&e450TCD1"2iM*5_]_L1*4MFj@($GagBGaS`>nWKm:oDP@`OEK!q>`$SJJ"QI%!*,jm%PoIdhD%-JG=`J]Dk?5[_J21>S^=qQsM]HE"S)!uCD0D&bo_G<uoE3P;+_F2Vc]UfJ)QA]tkAB&rK&'I>J6>**^T0iRiMFXEJ]Dh?4<'6j29(TX70%lhS8.qbTE(\rAURTW3Tnjk76H'@W-gVrK-Z!#[o"/D".?N":sh<rD`Ai=M2^k5=5\%#_i:ZsS\,EO&k?dN,K;Dt#rgMi-L'h0%krm.`R<H*a,<LpqMFIsQfalNY6r@^Tb!]Bi)H5N)$hjAB>7T!bH>",q4NNBM1f*?@Uh9Dkh(:rZZh$4f7#7akIR<u7B98pRp(ujSh&`3&V%m3%T,"c0BNS%K%KWf$:Ycg!df4DD:RM5-,g("Ue4u#Lbm.'U+FN%Bg+b)cH+W].K3L'6Qq\.SodV]G_D$QSX".Y_X4V!saoKO`#LZ9RIo';M#+-cXmDl^mM/aFmi7]oQiq<'rMbra95f*P>Y&^iurN7S*K-#m0Tn<0/cYu#$;R1\0\0OV2]o+BS*L(\CVRh+1a(h.JoW0_uBZ-=u@@qhlOL0QMC#q!p>k>BlD0i=sq,2)E1H.*5UWgA\D!O.e++o~>endstream
endobj
14 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1154
>>
stream
Gb!$H95bIu&BF88'QYf?EBN:5\jlll6Br+!DMUYK;q*9G:_id%N7Vi`dQD^)U`kVYh4=5_ci3f%$A.=Lf"YQ.!,n_g3Cc>dkq/PD)Q">?DE@VS_PX-0^j>"WCm29on_8lU0LoTE"\0`O.RYEe0i$#Dc/s!<A+oBp++MTX@=#DM]n5K39MYcK'7X%*]!;p".ogDQQg`W,LEA_LpO;c1$eoQ"l8(G%J?j%s23$7)>pC%$G#djZIuT*bs3s!/_mB%H$c<UH-:jJYalNFb-l]('e))[LE!V@7Mh\GaZ"OD9-'fId;Mh6)Y]O@=+Y90dH3h^8V;[]Q[L=gBEHifD9%LEoF(NgR+'2W*JoNs@:UhMF=P>JJ5]rsKR?D?l4P.Fj74n!g`5(D0K8r;.3Y"$sEN#Rk6OeG`C?Y\r`g2=dF<_<[ra1]gA()JK\m6d9SCJ+E@t*_/Nhh=OYG6GnlKrJci:7=?5OFOTG?!D@rUdWlrOkD9QHE`Tp&i9-j`As)DAIBP>jC7EhUjbgX`8OnG7:7%RaHbgV`q=1:m!LqI!FX1/Z9Q"@1VQ#;Vjta*r2qAC)+Y@YbrX:@4eB&o6P=@,2qP9Yf<Hd_NPFm0s%d]SWK]UKu9@7`XDFogj-U,TL2T.@QqBGbumhM1h@ufQBD]?`13dNOck5+1tn6-&c!8?(l@)n4MPnAKK2NMna^@%9F`+VE-jVYhF^j`aOeRtit\q@^e.(OE3mNY\Ye/>2@POD<GM`CP3?EE^V,)":@shZ,'OXN0bpcG`66j$h0_Dug$GCh$C#Dol1_+A3OGdIP1OG$Ltf7"$_b!jnl^kGltV=iBd[R@@Dd?'IETbb2,tM_.3[YZCKuSa7H3#T!tM"r$)N\0-#p'SdNhQ&QdqmbdRTLa\DDFDJp7Pq8go*E.>c50:kpH**C,f$8.S9a_X\>hTnkeK$<mSrTh%c-YdNk!`#N67/V3OjUa.K?lqg(j@Wa8RoMD>//>$30'VRmqXX0&MMNic?n%-N[+bn(]N0'4$E_G&P)9#]PZCObiMu"hDmQshM:`"sH\RL[oA`DHec]AZ*CUhPg9?CqJ[;)86>.7PXG8`!begd_2PuY9h"S44g-'W1:-@nk6;$j"`-lJ0bBF*U)'XZ@b,rNWU"a%2mHk&Vq!WE@Q`W~>endstream
endobj
15 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 742
>>
stream
Gatm:9lJc?%))O>n<auL,#[!IHe_*r$lt)*^(!.,SQiJ">q?Al^HYnbPrOU62_^Cs&];XZ;D4mZ,eq]5mq[IJ,qQ22OJ$t$bXE$'%=BY3h\emZlbF4Qc>8^$[X-.7L+*aFC5tW2Y)Pct`gD0[4uMZ+=Z6!hI/@-MTR<c?$RhX@]PJI'q:/usSI$k"b;#0/s311d]_TF:kNp5b[f,J;[('.edapdiMSn".L9h</dVAQW-.I>4?XDZ.Vr"n:i?%;%j:idn#3pj[TLP<XHd^d$)rK)b6ZSP=>/:i=HWA9\P%#*>6/!j-/$d#aPZ$p?@3EU3qh=`3kp)qHp#bU96`_&`%(FUddr$]V,mpEH#k52?%HkIf[SRRmm7,"$&kZmTiSEom#Ldtq-;>^*h5P!f*'Lm_g55GO4'$co2BBn-[&I0_q_.Se]<e#tpI)]c#c?a\Z2ad,A^OBi/T-KT]kO/hKN#F83kJ%Ip'45cWJCuiR5UNgX>[tsb8o=/L*i080E`(n@UTgk=0B4C2sjihR$35("V6rf.#bMcLU9*0QG'"WVr7N!*Uo,*2t6%HJTQ/U/^S0-jI?-g\_\CA>CPeC+A@gMA,F10TClWS+sicF0]hVHq#X'Jr@s;L"5/2cp7t"58Y[A8L:H/c4)*QJ+Z$'5A4<-,6&L>KS&)=d]dF7.eD@fU7nM6;,]U1JhBA7scQY;#iQ")$\nFGID5bQ<[VKn_1AAe3h0Pa(bollomXZ@[~>endstream
endobj
xref
0 16
0000000000 65535 f
0000000073 00000 n
0000000124 00000 n
0000000231 00000 n
0000000343 00000 n
0000000538 00000 n
0000000647 00000 n
0000000842 00000 n
0000001037 00000 n
0000001232 00000 n
0000001301 00000 n
0000001585 00000 n
0000001663 00000 n
0000002634 00000 n
0000003870 00000 n
0000005116 00000 n
trailer
<<
/ID
[<ee664bded2e591a430d712699aba8e65><ee664bded2e591a430d712699aba8e65>]
% ReportLab generated PDF document -- digest (http://www.reportlab.com)

/Info 10 0 R
/Root 9 0 R
/Size 16
>>
startxref
5949
%%EOF

+ 3
- 1
app/requirements.txt Datei anzeigen

@@ -6,4 +6,6 @@ alembic==1.12.0
passlib
python-jose==3.3.0
python-multipart==0.0.6
requests
requests
reportlab
python-dateutil

+ 1
- 1
docker-compose.yml Datei anzeigen

@@ -1,4 +1,4 @@
version: "3.11"
version: "3"

services:
backend:


+ 3
- 1
requirements.txt Datei anzeigen

@@ -6,4 +6,6 @@ alembic==1.12.0
passlib
python-jose==3.3.0
python-multipart==0.0.6
requests
requests
reportlab
python-dateutil

Laden…
Abbrechen
Speichern