diff --git a/worklenz-backend/database/sql/4_functions.sql b/worklenz-backend/database/sql/4_functions.sql index 56bae8ba..9c9cc820 100644 --- a/worklenz-backend/database/sql/4_functions.sql +++ b/worklenz-backend/database/sql/4_functions.sql @@ -6156,3 +6156,219 @@ BEGIN RETURN v_new_id; END; $$; + +CREATE OR REPLACE FUNCTION transfer_team_ownership(_team_id UUID, _new_owner_id UUID) RETURNS json + LANGUAGE plpgsql +AS +$$ +DECLARE + _old_owner_id UUID; + _owner_role_id UUID; + _admin_role_id UUID; + _old_org_id UUID; + _new_org_id UUID; + _has_license BOOLEAN; + _old_owner_role_id UUID; + _new_owner_role_id UUID; + _has_active_coupon BOOLEAN; + _other_teams_count INTEGER; + _new_owner_org_id UUID; + _license_type_id UUID; + _has_valid_license BOOLEAN; +BEGIN + -- Get the current owner's ID and organization + SELECT t.user_id, t.organization_id + INTO _old_owner_id, _old_org_id + FROM teams t + WHERE t.id = _team_id; + + IF _old_owner_id IS NULL THEN + RAISE EXCEPTION 'Team not found'; + END IF; + + -- Get the new owner's organization + SELECT organization_id INTO _new_owner_org_id + FROM organizations + WHERE user_id = _new_owner_id; + + -- Get the old organization + SELECT id INTO _old_org_id + FROM organizations + WHERE id = _old_org_id; + + IF _old_org_id IS NULL THEN + RAISE EXCEPTION 'Organization not found'; + END IF; + + -- Check if new owner has any valid license type + SELECT EXISTS ( + SELECT 1 + FROM ( + -- Check regular subscriptions + SELECT lus.user_id, lus.status, lus.active + FROM licensing_user_subscriptions lus + WHERE lus.user_id = _new_owner_id + AND lus.active = TRUE + AND lus.status IN ('active', 'trialing') + + UNION ALL + + -- Check custom subscriptions + SELECT lcs.user_id, lcs.subscription_status as status, TRUE as active + FROM licensing_custom_subs lcs + WHERE lcs.user_id = _new_owner_id + AND lcs.end_date > CURRENT_DATE + + UNION ALL + + -- Check trial status in organizations + SELECT o.user_id, o.subscription_status as status, TRUE as active + FROM organizations o + WHERE o.user_id = _new_owner_id + AND o.trial_in_progress = TRUE + AND o.trial_expire_date > CURRENT_DATE + ) valid_licenses + ) INTO _has_valid_license; + + IF NOT _has_valid_license THEN + RAISE EXCEPTION 'New owner does not have a valid license (subscription, custom subscription, or trial)'; + END IF; + + -- Check if new owner has any active coupon codes + SELECT EXISTS ( + SELECT 1 + FROM licensing_coupon_codes lcc + WHERE lcc.redeemed_by = _new_owner_id + AND lcc.is_redeemed = TRUE + AND lcc.is_refunded = FALSE + ) INTO _has_active_coupon; + + IF _has_active_coupon THEN + RAISE EXCEPTION 'New owner has active coupon codes that need to be handled before transfer'; + END IF; + + -- Count other teams in the organization for information purposes + SELECT COUNT(*) INTO _other_teams_count + FROM teams + WHERE organization_id = _old_org_id + AND id != _team_id; + + -- If new owner has their own organization, move the team to their organization + IF _new_owner_org_id IS NOT NULL THEN + -- Update the team to use the new owner's organization + UPDATE teams + SET user_id = _new_owner_id, + organization_id = _new_owner_org_id + WHERE id = _team_id; + + -- Create notification about organization change + PERFORM create_notification( + _old_owner_id, + _team_id, + NULL, + NULL, + CONCAT('Team ', (SELECT name FROM teams WHERE id = _team_id), ' has been moved to a different organization') + ); + + PERFORM create_notification( + _new_owner_id, + _team_id, + NULL, + NULL, + CONCAT('Team ', (SELECT name FROM teams WHERE id = _team_id), ' has been moved to your organization') + ); + ELSE + -- If new owner doesn't have an organization, transfer the old organization to them + UPDATE organizations + SET user_id = _new_owner_id + WHERE id = _old_org_id; + + -- Update the team to use the same organization + UPDATE teams + SET user_id = _new_owner_id, + organization_id = _old_org_id + WHERE id = _team_id; + + -- Notify both users about organization ownership transfer + PERFORM create_notification( + _old_owner_id, + NULL, + NULL, + NULL, + CONCAT('You are no longer the owner of organization ', (SELECT organization_name FROM organizations WHERE id = _old_org_id), '') + ); + + PERFORM create_notification( + _new_owner_id, + NULL, + NULL, + NULL, + CONCAT('You are now the owner of organization ', (SELECT organization_name FROM organizations WHERE id = _old_org_id), '') + ); + END IF; + + -- Get the owner and admin role IDs + SELECT id INTO _owner_role_id FROM roles WHERE team_id = _team_id AND owner = TRUE; + SELECT id INTO _admin_role_id FROM roles WHERE team_id = _team_id AND admin_role = TRUE; + + -- Get current role IDs for both users + SELECT role_id INTO _old_owner_role_id + FROM team_members + WHERE team_id = _team_id AND user_id = _old_owner_id; + + SELECT role_id INTO _new_owner_role_id + FROM team_members + WHERE team_id = _team_id AND user_id = _new_owner_id; + + -- Update the old owner's role to admin if they want to stay in the team + IF _old_owner_role_id IS NOT NULL THEN + UPDATE team_members + SET role_id = _admin_role_id + WHERE team_id = _team_id AND user_id = _old_owner_id; + END IF; + + -- Update the new owner's role to owner + IF _new_owner_role_id IS NOT NULL THEN + UPDATE team_members + SET role_id = _owner_role_id + WHERE team_id = _team_id AND user_id = _new_owner_id; + ELSE + -- If new owner is not a team member yet, add them + INSERT INTO team_members (user_id, team_id, role_id) + VALUES (_new_owner_id, _team_id, _owner_role_id); + END IF; + + -- Create notification for both users about team ownership + PERFORM create_notification( + _old_owner_id, + _team_id, + NULL, + NULL, + CONCAT('You are no longer the owner of team ', (SELECT name FROM teams WHERE id = _team_id), '') + ); + + PERFORM create_notification( + _new_owner_id, + _team_id, + NULL, + NULL, + CONCAT('You are now the owner of team ', (SELECT name FROM teams WHERE id = _team_id), '') + ); + + RETURN json_build_object( + 'success', TRUE, + 'old_owner_id', _old_owner_id, + 'new_owner_id', _new_owner_id, + 'team_id', _team_id, + 'old_org_id', _old_org_id, + 'new_org_id', COALESCE(_new_owner_org_id, _old_org_id), + 'old_role_id', _old_owner_role_id, + 'new_role_id', _new_owner_role_id, + 'has_valid_license', _has_valid_license, + 'has_active_coupon', _has_active_coupon, + 'other_teams_count', _other_teams_count, + 'org_ownership_transferred', _new_owner_org_id IS NULL, + 'team_moved_to_new_org', _new_owner_org_id IS NOT NULL + ); +END; +$$; diff --git a/worklenz-backend/worklenz-email-templates/password-changed-notification.html b/worklenz-backend/worklenz-email-templates/password-changed-notification.html index f734d8a8..2c8e2d3a 100644 --- a/worklenz-backend/worklenz-email-templates/password-changed-notification.html +++ b/worklenz-backend/worklenz-email-templates/password-changed-notification.html @@ -2,31 +2,30 @@ - + Password Changed | Worklenz + - - - - + + + + + + + + + diff --git a/worklenz-backend/worklenz-email-templates/reset-password.html b/worklenz-backend/worklenz-email-templates/reset-password.html index d6f7e4d7..9c5f2c24 100644 --- a/worklenz-backend/worklenz-email-templates/reset-password.html +++ b/worklenz-backend/worklenz-email-templates/reset-password.html @@ -2,31 +2,30 @@ - + Reset Your Password | Worklenz + - - - - - - - - + + + + diff --git a/worklenz-backend/worklenz-email-templates/team-invitation.html b/worklenz-backend/worklenz-email-templates/team-invitation.html index 921e845d..f0d17e33 100644 --- a/worklenz-backend/worklenz-email-templates/team-invitation.html +++ b/worklenz-backend/worklenz-email-templates/team-invitation.html @@ -2,31 +2,30 @@ - + Join Your Team on Worklenz + - - - - - - - - + + + + diff --git a/worklenz-backend/worklenz-email-templates/unregistered-team-invitation-notification.html b/worklenz-backend/worklenz-email-templates/unregistered-team-invitation-notification.html index a231f9ad..2db5cfc2 100644 --- a/worklenz-backend/worklenz-email-templates/unregistered-team-invitation-notification.html +++ b/worklenz-backend/worklenz-email-templates/unregistered-team-invitation-notification.html @@ -2,31 +2,30 @@ - + Join Your Team on Worklenz + - - - - - + + + + + + + + + + + + + + + + diff --git a/worklenz-backend/worklenz-email-templates/welcome.html b/worklenz-backend/worklenz-email-templates/welcome.html index bc258a6d..7bb62821 100644 --- a/worklenz-backend/worklenz-email-templates/welcome.html +++ b/worklenz-backend/worklenz-email-templates/welcome.html @@ -2,31 +2,30 @@ - + Welcome to Worklenz + - - - - - + + + + + + + + + + + + + + + +