feat(tenancy): add resend invitation for pending team members
Some checks failed
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
CI / ruff (push) Successful in 14s

New resend_invitation() service method regenerates the token and
resends the invitation email for pending members.

Available on all frontends:
- Merchant: POST /merchants/account/team/stores/{sid}/members/{uid}/resend
- Store: POST /store/team/members/{uid}/resend

UI: paper-airplane icon appears on pending members in both merchant
and store team pages.

i18n: resend_invitation + invitation_resent keys in 4 locales.
Also translated previously untranslated invitation_sent_successfully
in fr/de/lb.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 16:48:12 +02:00
parent dab5560de8
commit 823935c016
11 changed files with 175 additions and 3 deletions

View File

@@ -907,6 +907,69 @@ class StoreTeamService:
db.flush()
return role
def resend_invitation(
self,
db: Session,
store: Store,
user_id: int,
inviter: User,
) -> dict[str, Any]:
"""
Resend invitation to a pending team member.
Generates a new token and resends the email.
Only works for pending invitations (not yet accepted).
"""
store_user = (
db.query(StoreUser)
.filter(
StoreUser.store_id == store.id,
StoreUser.user_id == user_id,
)
.first()
)
if not store_user:
raise UserNotFoundException(str(user_id))
if store_user.invitation_accepted_at is not None:
raise InvalidInvitationTokenException("Invitation already accepted")
# Generate new token and update sent time
new_token = self._generate_invitation_token()
store_user.invitation_token = new_token
store_user.invitation_sent_at = datetime.utcnow()
db.flush()
# Get role name for email
role_name = store_user.role.name if store_user.role else "member"
# Send email
try:
self._send_invitation_email(
db=db,
email=store_user.user.email,
store=store,
token=new_token,
inviter=inviter,
role_name=role_name,
)
except Exception: # noqa: EXC003
logger.exception(
f"Failed to resend invitation email to {store_user.user.email}"
)
logger.info(
f"Resent invitation to {store_user.user.email} for store "
f"{store.store_code} by {inviter.username}"
)
return {
"email": store_user.user.email,
"store_code": store.store_code,
"invitation_sent": True,
}
def _send_invitation_email(
self,
db: Session,