nordabiz/templates/board/meeting_admissions.html
Maciej Pienczyn 90203676e5
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
feat: Post-Rada Workflow Engine + redesign /nowi-czlonkowie
- AdmissionWorkflowLog model + migration
- services/admission_workflow.py — auto-extract admitted companies from
  board meeting proceedings, match to existing companies, create
  placeholders, notify Office Managers (in-app + email)
- Hook in meeting_publish_protocol() — triggers workflow in background
- Dashboard /rada/posiedzenia/ID/przyjecia for Office Manager
- /nowi-czlonkowie redesigned: grouped by board meetings, fixed Polish
  characters, removed days-based filter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 23:51:38 +02:00

216 lines
7.4 KiB
HTML

{% extends "base.html" %}
{% block title %}Przyjęcia nowych członków — Posiedzenie {{ meeting.meeting_identifier }} — Norda Biznes Partner{% endblock %}
{% block extra_css %}
<style>
.admissions-header {
margin-bottom: var(--spacing-xl);
}
.admissions-header h1 {
font-size: var(--font-size-2xl);
color: var(--text-primary);
}
.admissions-header p {
color: var(--text-secondary);
margin-top: var(--spacing-xs);
}
.admissions-stats {
display: flex;
gap: var(--spacing-lg);
margin-bottom: var(--spacing-xl);
}
.stat-card {
background: var(--surface);
border-radius: var(--radius);
padding: var(--spacing-md) var(--spacing-lg);
border: 1px solid var(--border);
text-align: center;
min-width: 120px;
}
.stat-card .stat-value {
font-size: var(--font-size-2xl);
font-weight: 700;
color: var(--primary);
}
.stat-card .stat-label {
font-size: var(--font-size-sm);
color: var(--text-secondary);
margin-top: 2px;
}
.stat-card.pending .stat-value { color: #D97706; }
.stat-card.active .stat-value { color: var(--success); }
.companies-table {
width: 100%;
border-collapse: collapse;
background: var(--surface);
border-radius: var(--radius);
overflow: hidden;
border: 1px solid var(--border);
}
.companies-table th {
text-align: left;
padding: var(--spacing-sm) var(--spacing-md);
background: var(--background);
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.companies-table td {
padding: var(--spacing-sm) var(--spacing-md);
border-top: 1px solid var(--border);
font-size: var(--font-size-sm);
}
.status-badge {
display: inline-block;
padding: 2px 8px;
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
font-weight: 600;
}
.status-badge.active {
background: #D1FAE5;
color: #065F46;
}
.status-badge.pending {
background: #FEF3C7;
color: #92400E;
}
.action-btn {
display: inline-block;
padding: 4px 12px;
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
font-weight: 500;
text-decoration: none;
border: 1px solid var(--border);
color: var(--text-primary);
transition: var(--transition);
}
.action-btn:hover {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.workflow-log {
margin-top: var(--spacing-xl);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: var(--spacing-md);
}
.workflow-log h3 {
font-size: var(--font-size-base);
margin-bottom: var(--spacing-sm);
color: var(--text-secondary);
}
.workflow-log .log-item {
font-size: var(--font-size-sm);
color: var(--text-secondary);
padding: 2px 0;
}
</style>
{% endblock %}
{% block content %}
<div class="admissions-header">
<a href="{{ url_for('board.meeting_view', meeting_id=meeting.id) }}" style="color: var(--text-secondary); text-decoration: none; font-size: var(--font-size-sm);">← Powrót do posiedzenia</a>
<h1>Przyjęcia nowych członków</h1>
<p>Posiedzenie Rady {{ meeting.meeting_identifier }} — {{ meeting.meeting_date.strftime('%d.%m.%Y') }}</p>
</div>
{% set active_count = admitted|selectattr('status', 'equalto', 'active')|list|length %}
{% set pending_count = admitted|length - active_count %}
<div class="admissions-stats">
<div class="stat-card">
<div class="stat-value">{{ admitted|length }}</div>
<div class="stat-label">Przyjętych firm</div>
</div>
<div class="stat-card active">
<div class="stat-value">{{ active_count }}</div>
<div class="stat-label">Profil uzupełniony</div>
</div>
<div class="stat-card pending">
<div class="stat-value">{{ pending_count }}</div>
<div class="stat-label">Wymaga uzupełnienia</div>
</div>
</div>
{% if admitted %}
<table class="companies-table">
<thead>
<tr>
<th>Firma</th>
<th>Status profilu</th>
<th>NIP</th>
<th>Kategoria</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
{% for company in admitted %}
<tr>
<td>
<strong>{{ company.name }}</strong>
{% if company.address_city %}
<br><span style="color: var(--text-muted); font-size: var(--font-size-xs);">{{ company.address_city }}</span>
{% endif %}
</td>
<td>
{% if company.status == 'active' %}
<span class="status-badge active">Aktywny</span>
{% else %}
<span class="status-badge pending">Do uzupełnienia</span>
{% endif %}
</td>
<td>{{ company.nip or '—' }}</td>
<td>{{ company.category.name if company.category else '—' }}</td>
<td>
{% if company.status == 'active' %}
<a href="{{ url_for('public.company_detail_by_slug', slug=company.slug) }}" class="action-btn" target="_blank">Profil</a>
{% else %}
<a href="{{ url_for('admin.admin_company_detail', company_id=company.id) }}" class="action-btn" style="border-color: #D97706; color: #92400E;">Uzupełnij</a>
{% endif %}
<a href="{{ url_for('admin.admin_company_detail', company_id=company.id) }}" class="action-btn">Edytuj</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div style="text-align: center; padding: var(--spacing-2xl); color: var(--text-secondary); background: var(--surface); border-radius: var(--radius);">
<p>Brak firm przypisanych do tego posiedzenia.</p>
<p style="font-size: var(--font-size-sm); margin-top: var(--spacing-sm);">Firmy zostaną automatycznie przypisane po opublikowaniu protokołu.</p>
</div>
{% endif %}
{% if workflow_log %}
<div class="workflow-log">
<h3>Log workflow przyjęć</h3>
<div class="log-item">Wykonano: {{ workflow_log.executed_at|local_time }}</div>
<div class="log-item">Status: {{ workflow_log.status }}</div>
{% if workflow_log.extracted_companies %}
<div class="log-item">Wyodrębniono z protokołu: {{ workflow_log.extracted_companies|length }} firm</div>
{% endif %}
{% if workflow_log.matched_companies %}
<div class="log-item">Dopasowano do istniejących: {{ workflow_log.matched_companies|length }}</div>
{% endif %}
{% if workflow_log.created_companies %}
<div class="log-item">Utworzono nowe profile: {{ workflow_log.created_companies|length }}</div>
{% endif %}
{% if workflow_log.skipped %}
<div class="log-item">Pominięto (już przypisane): {{ workflow_log.skipped|length }}</div>
{% endif %}
<div class="log-item">Powiadomienia: {{ workflow_log.notifications_sent }} in-app, {{ workflow_log.emails_sent }} email</div>
{% if workflow_log.error_message %}
<div class="log-item" style="color: var(--error);">Błąd: {{ workflow_log.error_message }}</div>
{% endif %}
</div>
{% endif %}
{% endblock %}