feat(tenancy): add merchant team CRUD with multi-store hub view

The merchant team page was read-only. Now merchant owners can invite,
edit roles, and remove team members across all their stores from a
single hub view.

Architecture: No new models — delegates to existing store_team_service.
Members are deduplicated across stores with per-store role badges.

New:
- 5 API endpoints: GET team (member-centric), GET store roles, POST
  invite (multi-store), PUT update role, DELETE remove member
- merchant-team.js Alpine component with invite/edit/remove modals
- Full CRUD template with stats cards, store filter, member table
- 7 Pydantic schemas for merchant team request/response
- 2 service methods: validate_store_ownership, get_merchant_team_members
- 25 new i18n keys across 4 tenancy locales + 1 core common key

Tests: 434 tenancy tests passing, arch-check green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 18:57:45 +01:00
parent aaed1b2d01
commit 0455e63a2e
14 changed files with 1131 additions and 158 deletions

View File

@@ -12,20 +12,45 @@
"team": "Team"
},
"team": {
"title": "Team",
"members": "Memberen",
"actions": "Aktiounen",
"active": "Aktiv",
"add_member": "Member derbäisetzen",
"all_stores": "All Geschäfter",
"edit_member": "Member änneren",
"editor": "Editeur",
"email": "E-Mail",
"email_placeholder": "E-Mail-Adress aginn",
"error_title": "Feeler beim Lueden vum Team",
"first_name": "Virnumm",
"invite_first_member": "Invitéiert Äert éischt Teammember",
"invite_member": "Member invitéieren",
"invitation_accepted": "Invitatioun ugeholl",
"invitation_sent": "Invitatioun geschéckt",
"last_name": "Nonumm",
"loading_team": "Team gëtt gelueden...",
"manage_members_description": "Teammemberen iwwer all Geschäfter verwalten",
"manager": "Manager",
"member": "Member",
"member_stores": "Geschäfter vum Member",
"members": "Memberen",
"no_members_description": "Invitéiert Teammemberen fir Är Geschäfter ze verwalten",
"no_members_title": "Nach keng Teammemberen",
"no_role": "Keng Roll",
"owner": "Proprietär",
"pending_invitations": "Aussteesend Invitatiounen",
"permissions": "Rechter",
"remove_confirmation": "Sidd Dir sécher, datt Dir ewechhuele wëllt",
"remove_from_all_stores": "Vun all Geschäfter ewechhuelen",
"remove_member": "Member ewechhuelen",
"role": "Roll",
"owner": "Proprietär",
"manager": "Manager",
"editor": "Editeur",
"viewer": "Betruechter",
"permissions": "Rechter",
"pending_invitations": "Aussteesend Invitatiounen",
"invitation_sent": "Invitatioun geschéckt",
"invitation_accepted": "Invitatioun ugeholl"
"select_stores": "Geschäfter wielen",
"send_invitation": "Invitatioun schécken",
"status": "Status",
"store_roles": "Geschäftsrollen",
"stores_and_roles": "Geschäfter & Rollen",
"title": "Team",
"total_members": "Memberen total",
"viewer": "Betruechter"
},
"messages": {
"business_info_saved": "Business info saved",