3911012c6538203e7cce5eb6228fecf79ff85185
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3911012c65 |
feat(i18n): mega-wrap 494 raw RO strings + 269 lang entries + 161 human RU/163 EN
Broad sweep across the whole codebase:
- Blade views (39 files, 315 wraps): tag-text and title/placeholder/alt
attributes wrapped with {{ __() }}. Excludes scripts, styles, @php,
@verbatim, {{ }}, {!! !!}, comments to avoid touching interpolations.
- PHP (54 files, 179 wraps): array 'key' => 'RO value' patterns and
list items with diacritics wrapped with __(). Reverted __() inside
const arrays (PHP disallows non-constant expressions).
- Added 269 new keys to lang/{ru,en}.json (identity fallback for
unknowns → 161 human RU + 163 EN translations added for the most
common enums, stages, roles, statuses, payment methods, vehicle
categories, warehouse, portal, form actions.
Missing translations fall back to RO so the UI never breaks. All 306
tests pass; view cache compiles cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d9180e16b3 |
feat: P2 RBAC defers — REST API + invitation workflow
Closes the P2 items from /tmp/service/new/01-TZ-rbac §4.1 §4.2.
== User invitation workflow ==
New columns on users: invited_at, invited_by_id (FK self), accepted_at,
invitation_token (sha256 hash, indexed). Migration is idempotent.
User::sendInvitation($invitedBy = auth()->user())
- generates 64-char random token
- stores sha256(token) in invitation_token column (never plaintext)
- marks invited_at = now(), status = inactive
- queues UserInvitationMail to the user's email with the signed accept URL
- returns the raw token (for tests / API consumers)
User::findByInvitationToken($rawToken) hashes + lookups.
User::acceptInvitation($password) sets password (hashed cast), clears
invitation_token, marks accepted_at + email_verified_at, status = active.
Web routes (no auth — token IS the credential):
GET /invitations/{token} → password-set form
POST /invitations/{token} → validates min:8 + confirmed, accepts
Tokens expire after 7 days (checked against invited_at). Expired and
invalid tokens render dedicated views (invitations/expired.blade.php,
invitations/invalid.blade.php) instead of generic 404 — so the user
knows to ask for a resend.
UserInvitationMail uses Filament's existing markdown layout; subject
includes the tenant display_name.
== REST API ==
Twenty new endpoints under /api/v1/ (Sanctum auth + tenant scoping
via the existing EnsureTokenMatchesTenant middleware). All gated by
ADMIN_USERS_* / ADMIN_ROLES_MANAGE permissions; mechanic-level token
gets 403.
Users:
GET /users — paginated + role/status/q filters
GET /users/{u} — eager-loads roles + overrides + invitedBy
POST /users — creates inactive user + sends invitation
PATCH /users/{u} — update name/email/role/status
DELETE /users/{u} — soft delete
POST /users/{u}/activate
POST /users/{u}/deactivate — also revokes all sessions
POST /users/{u}/resend-invitation
POST /users/{u}/force-password-reset — re-sends invitation
GET /users/{u}/sessions — list active sessions (from sessions table)
DELETE /users/{u}/sessions — revoke all
DELETE /users/{u}/sessions/{sessionId} — revoke one
GET /users/{u}/roles — assigned roles
POST /users/{u}/roles — assign role
DELETE /users/{u}/roles/{role} — remove role
GET /users/{u}/permissions — effective: role perms + grants - active denies
POST /users/{u}/permission-overrides — add grant/deny (with optional expires_at)
DELETE /users/{u}/permission-overrides/{perm}
Roles:
apiResource roles — index/show/store/update/destroy
(system roles guarded against rename/delete)
GET /roles/{r}/permissions
PUT /roles/{r}/permissions — bulk sync
GET /permissions — catalog: flat list + grouped + labels + role labels
Authorization is uniform: every controller method calls $this->authorize()
which throws 403 if canDo(perm) is false. canDo() already honors the
overrides + admin bypass + audit log from earlier commits, so the API
behaves identically to the Filament UI.
== Tests ==
InvitationFlowTest (8): token generation + sha256 storage + queued mail,
findByInvitationToken happy/sad path, accept sets password + activates,
GET form renders, POST accepts + redirects, invalid token view,
backdated invited_at → expired view, password too short → validation error.
RbacApiTest (12): admin can list users, mechanic 403, create user
queues invitation, assign+remove role round-trip, effective permissions
endpoint subtracts active denies, add+remove override via API,
role index returns 7 system roles with permission counts (51 for owner),
role sync permissions, system role destroy rejected with 422,
permission catalog endpoint returns all 51 + grouped + labels,
revoke all sessions deletes only target user's rows.
Suite: 234 passed (659 assertions). Was 214.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
3da1f5412a |
feat: shop UX polish — password reset / order email / multi-image / customer admin
Shop password reset:
- Configured 'shop_customers' password broker on the existing
password_reset_tokens table
- ShopCustomer::sendPasswordResetNotification overrides Laravel default to
send a ShopPasswordResetMail with a tenant-subdomain reset URL
- Routes /shop/password/forgot, /shop/password/email, /shop/password/reset/{token}
+ ShopAuthController showForgotPassword/sendResetLink/showResetPassword/
resetPassword. Forgot view stays generic ("if it exists, we sent…") to avoid
email enumeration. Login view links to "Am uitat parola".
Order confirmation email:
- ShopOrderConfirmationMail + nicely formatted HTML email template
- ShopOrderNotifier::placed now also emails customer_email (best-effort,
warning-only logged on failure) alongside existing Telegram + staff push
Multiple images per Part:
- Part media collection switched from singleFile to multiple (max 8 in form)
- imageUrls() helper for galleries; imageUrl() still returns first for cards
- PartResource form: reorderable multi-upload
- Shop part detail: vertical thumbnails switch the main image via vanilla JS
ShopCustomerResource (tenant Filament, "Magazin" nav group):
- List with name/phone/email/client_id/orders_count/last_login_at
- Edit (no password field exposed)
- "Trimite reset parolă" action uses the new broker
- OrdersRelationManager shows the customer's orders read-only
Tests (7 new):
- forgot sends mail; forgot doesn't disclose unknown email; reset with valid
token changes password; bad token rejected; order email when customer_email
set; email skipped without it; Part has imageUrls() collection
Full suite: 130 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
09fd0bada2 |
Faza 2 (din continuare): Email notifications
4 Mailables auto-trigger pe model events: - WorkOrderReadyMail: la WO.status → 'ready', către client.email • Atașat PDF fișa lucru (via WorkOrderPdfService) • Total/achitat/rest, recomandări (warning box) - PaymentReceivedMail: la Payment::created, confirmare cu sumă/metodă/ref - AppointmentConfirmedMail: la Appointment::created status='scheduled' - ServiceReminderMail: dispatch manual (vehicle, type=itp/oil/general, note) Layout email branded (resources/views/emails/layout.blade.php): - Header cu logo tenant + theme_color border-bottom - Footer cu telefon/email/disclaimer - Stiluri inline (compatibil tot mail client) Settings page extins cu 4 toggle: - 'Mașina e gata de ridicat' - 'Confirmare plată primită' - 'Programare confirmată' - 'Reminder ITP / revizie' Salvate în companies.settings.notify (JSON), default true. NotificationDispatcher service centralizat: - Verifică isEnabled() pe settings.notify[$key] - Skip dacă client n-are email - Try/catch + Log::warning pe eșec (nu crapă request-ul) Mailables folosesc UsesTenantBranding trait pentru context unitar. Test prin Mailpit: https://mailpit.service.mir.md (capturează toate). |