Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 96771d192c |
+44
-1
@@ -166,7 +166,7 @@ SCHEDULER_INTERVAL = cfg.SCHEDULER_INTERVAL_MIN
|
||||
SSL_CERT = cfg.SSL_CERT
|
||||
SSL_KEY = cfg.SSL_KEY
|
||||
|
||||
LIBRARY_ITEM_TYPES = ['book', 'cd', 'dvd', 'media']
|
||||
LIBRARY_ITEM_TYPES = ['book', 'cd', 'dvd', 'media', 'schulbuch']
|
||||
INVOICE_CURRENCY = 'EUR'
|
||||
|
||||
NOTIFICATION_STATUS_CACHE_TTL = max(3, int(os.getenv('INVENTAR_NOTIFICATION_STATUS_CACHE_TTL', '8')))
|
||||
@@ -2717,6 +2717,49 @@ def tutorial_page():
|
||||
)
|
||||
|
||||
@app.route('/library')
|
||||
|
||||
@app.route('/library/export/<scope>')
|
||||
def library_export_excel(scope):
|
||||
if 'username' not in session:
|
||||
return redirect(url_for('login'))
|
||||
|
||||
username = session['username']
|
||||
is_admin_user = us.check_admin(username)
|
||||
|
||||
import excel_export
|
||||
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
items_collection = db['items']
|
||||
|
||||
query = {'ItemType': {'$in': LIBRARY_ITEM_TYPES}, 'Deleted': {'$ne': True}}
|
||||
|
||||
if scope == 'borrowed_by_me':
|
||||
query['User'] = username
|
||||
query['Verfuegbar'] = False
|
||||
filename = f"Bibliothek_Ausgeliehen_{username}.xlsx"
|
||||
elif scope == 'all_borrowed':
|
||||
if not is_admin_user:
|
||||
return redirect(url_for('library_view'))
|
||||
query['Verfuegbar'] = False
|
||||
filename = "Bibliothek_Alle_Ausleihen.xlsx"
|
||||
elif scope == 'schulbuecher':
|
||||
query['ItemType'] = 'schulbuch'
|
||||
filename = "Schulbuecher_Bestand.xlsx"
|
||||
else:
|
||||
# 'all'
|
||||
filename = "Bibliothek_Gesamtbestand.xlsx"
|
||||
|
||||
items = list(items_collection.find(query))
|
||||
client.close()
|
||||
|
||||
excel_file = excel_export.generate_library_excel(items)
|
||||
return flask.send_file(
|
||||
excel_file,
|
||||
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
as_attachment=True,
|
||||
download_name=filename
|
||||
)
|
||||
|
||||
def library_view():
|
||||
"""
|
||||
Dedicated page for viewing library items (books, CDs, etc.).
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import io
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, PatternFill
|
||||
|
||||
def generate_library_excel(items):
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Export"
|
||||
|
||||
headers = [
|
||||
"Code", "Titel", "Autor", "Typ", "ISBN/Code",
|
||||
"Filter 1", "Filter 2", "Filter 3",
|
||||
"Status", "Ausgeliehen von", "Rückgabe", "Kosten"
|
||||
]
|
||||
|
||||
ws.append(headers)
|
||||
|
||||
header_font = Font(bold=True, color="FFFFFF")
|
||||
header_fill = PatternFill(start_color="1F2937", end_color="1F2937", fill_type="solid")
|
||||
for cell in ws[1]:
|
||||
cell.font = header_font
|
||||
cell.fill = header_fill
|
||||
|
||||
for item in items:
|
||||
status = "Verfuegbar" if str(item.get("Verfuegbar", "True")).lower() == "true" else "Ausgeliehen"
|
||||
row = [
|
||||
item.get("Code_4", ""),
|
||||
item.get("Name", ""),
|
||||
item.get("Author", ""),
|
||||
item.get("ItemType", ""),
|
||||
item.get("ISBN", ""),
|
||||
item.get("Filter", ""),
|
||||
item.get("Filter2", ""),
|
||||
item.get("Filter3", ""),
|
||||
status,
|
||||
item.get("User", ""),
|
||||
item.get("ReturnDate", ""),
|
||||
item.get("Anschaffungskosten", "")
|
||||
]
|
||||
ws.append(row)
|
||||
|
||||
for col in ws.columns:
|
||||
max_length = 0
|
||||
column = col[0].column_letter
|
||||
for cell in col:
|
||||
try:
|
||||
if len(str(cell.value)) > max_length:
|
||||
max_length = len(str(cell.value))
|
||||
except:
|
||||
pass
|
||||
ws.column_dimensions[column].width = min(max_length + 2, 50)
|
||||
|
||||
excel_buffer = io.BytesIO()
|
||||
wb.save(excel_buffer)
|
||||
excel_buffer.seek(0)
|
||||
return excel_buffer
|
||||
+9
-10
@@ -1096,16 +1096,6 @@
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}" data-tutorial-tip="Ihre aktuellen Ausleihen und Rückgaben finden Sie hier.">Meine Ausleihen</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('tutorial_page') %}nav-active{% endif %}" href="{{ url_for('tutorial_page') }}" data-tutorial-tip="Öffnen Sie das Tutorial, um das System Schritt für Schritt kennenzulernen.">Tutorial</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if 'username' in session and current_permissions.pages.get('upload_admin', True) and current_permissions.actions.get('can_insert', True) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link nav-priority-link {% if current_path == url_for('upload_admin') %}nav-active{% endif %}" href="{{ url_for('upload_admin') }}" data-tutorial-tip="Hier können Sie neue Artikel ins Inventar einpflegen.">➕ Hochladen</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item" data-nav-fixed="true">
|
||||
<button id="themeToggleBtn" class="btn btn-link nav-link px-3" aria-label="Dark Mode umschalten" title="Theme umschalten">
|
||||
@@ -1119,6 +1109,15 @@
|
||||
Mehr Optionen
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="invMoreDropdown">
|
||||
{% if 'username' in session %}
|
||||
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('tutorial_page') }}">Tutorial</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('upload_admin', True) and current_permissions.actions.get('can_insert', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('upload_admin') }}">➕ Hochladen</a></li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{% endif %}
|
||||
{% if 'username' in session and (session.get('admin', False) or is_admin) and current_permissions.actions.get('can_manage_settings', True) %}
|
||||
<li><h6 class="dropdown-header">Verwaltung</h6></li>
|
||||
{% if current_permissions.pages.get('manage_filters', True) %}
|
||||
|
||||
@@ -477,6 +477,7 @@
|
||||
<select id="filterType">
|
||||
<option value="">-- Alle Typen --</option>
|
||||
<option value="book">Buch</option>
|
||||
<option value="schulbuch">Schulbuch</option>
|
||||
<option value="cd">CD</option>
|
||||
<option value="dvd">DVD</option>
|
||||
<option value="other">Sonstige</option>
|
||||
@@ -495,6 +496,15 @@
|
||||
<div class="filter-buttons">
|
||||
<button id="applyFilterBtn" class="button" style="background: #4f46e5; color: white;">Filter anwenden</button>
|
||||
<button id="clearFilterBtn" class="button" style="background: #f0f2f5; color: var(--ui-text);">Zurücksetzen</button>
|
||||
<div style="flex-grow: 1;"></div>
|
||||
<button class="button" style="background: #10b981; color: white; display: flex; align-items: center; gap: 0.5rem;" onclick="window.location.href='/library/export/schulbuecher'">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="8" y1="13" x2="16" y2="13"></line><line x1="8" y1="17" x2="16" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
||||
Schulbücher Excel
|
||||
</button>
|
||||
<button class="button" style="background: #10b981; color: white; display: flex; align-items: center; gap: 0.5rem;" onclick="window.location.href='/library/export/all_borrowed'">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="8" y1="13" x2="16" y2="13"></line><line x1="8" y1="17" x2="16" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
||||
Alle ausgeliehen Excel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1174,6 +1184,7 @@
|
||||
<label for="editLibraryType">Medientyp</label>
|
||||
<select id="editLibraryType">
|
||||
<option value="book">Buch</option>
|
||||
<option value="schulbuch">Schulbuch</option>
|
||||
<option value="cd">CD</option>
|
||||
<option value="dvd">DVD</option>
|
||||
<option value="media">Sonstige Medien</option>
|
||||
|
||||
+35
-36
@@ -3163,46 +3163,45 @@
|
||||
border: 1px solid var(--ui-border);
|
||||
border-radius: var(--ui-radius);
|
||||
box-shadow: var(--ui-shadow-sm);
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
max-width: 340px;
|
||||
flex: 1 1 300px;
|
||||
scroll-snap-align: center;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
position: relative;
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 300px 450px;
|
||||
}
|
||||
.item-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
max-width: 340px;
|
||||
flex: 1 1 300px;
|
||||
scroll-snap-align: center;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
position: relative;
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 300px 380px;
|
||||
}
|
||||
.item-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.item-card h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.item-card h3 {
|
||||
margin-top: 0;
|
||||
font-size: 1.15rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.item-card .description {
|
||||
max-height: 100px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.item-card .description {
|
||||
max-height: 60px;
|
||||
overflow-y: auto;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.item-card .image-container {
|
||||
position: relative;
|
||||
max-width: 300px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.item-card .image-container {
|
||||
position: relative;
|
||||
max-width: 300px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.item-card .item-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 300px;
|
||||
display: none;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.item-card .item-image:first-child {
|
||||
.item-card .item-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 180px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
@@ -395,7 +395,7 @@
|
||||
border: 1px solid var(--ui-border);
|
||||
border-radius: var(--ui-radius);
|
||||
box-shadow: var(--ui-shadow-sm);
|
||||
padding: 20px;
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
max-width: 340px;
|
||||
flex: 1 1 300px;
|
||||
@@ -404,7 +404,7 @@
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
position: relative;
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 300px 450px;
|
||||
contain-intrinsic-size: 300px 380px;
|
||||
}
|
||||
|
||||
.item-card.bulk-selected {
|
||||
@@ -420,17 +420,21 @@
|
||||
|
||||
.item-card h3 {
|
||||
margin-top: 0;
|
||||
font-size: 1.15rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.item-card .description {
|
||||
max-height: 100px;
|
||||
max-height: 60px;
|
||||
overflow-y: auto;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.item-card .image-container {
|
||||
position: relative;
|
||||
max-width: 300px;
|
||||
margin: 10px 0;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.bulk-delete-row {
|
||||
@@ -479,7 +483,7 @@
|
||||
.item-card .item-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 300px;
|
||||
max-height: 180px;
|
||||
display: none;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user