diff --git a/forms_pro/api/team.py b/forms_pro/api/team.py index 58bfa09..bcc8f33 100644 --- a/forms_pro/api/team.py +++ b/forms_pro/api/team.py @@ -1,7 +1,7 @@ import frappe from forms_pro.forms_pro.doctype.fp_team.fp_team import FPTeam, GetTeamMembersResponse -from forms_pro.utils.teams import GetTeamFormsResponseSchema +from forms_pro.utils.teams import GetTeamFormsResponseSchema, set_current_team from forms_pro.utils.teams import get_team_forms as get_team_forms_utils @@ -35,9 +35,43 @@ def get_team_members(team_id: str) -> list[GetTeamMembersResponse]: user=frappe.session.user, throw=True, ) - print("team_id", team_id) team: FPTeam = frappe.get_doc("FP Team", team_id) members = team.team_members return members + + +@frappe.whitelist(methods=["POST"]) +def create_team(team_name: str) -> FPTeam: + """ + Create a new team + + Args: + team_name: Name of the team + + Returns: + FPTeam - The created team + """ + + team: FPTeam = frappe.new_doc("FP Team") + team.team_name = team_name + team.insert() + return team + + +@frappe.whitelist(methods=["POST"]) +def switch_team(team_id: str) -> None: + """ + Switch to a new team + """ + + if not frappe.has_permission( + doctype="FP Team", + ptype="read", + doc=team_id, + user=frappe.session.user, + ): + raise frappe.PermissionError("You do not have permission to switch to this team") + + set_current_team(team_id, frappe.session.user) diff --git a/forms_pro/forms_pro/doctype/fp_team/fp_team.py b/forms_pro/forms_pro/doctype/fp_team/fp_team.py index e717cb8..4ecc550 100644 --- a/forms_pro/forms_pro/doctype/fp_team/fp_team.py +++ b/forms_pro/forms_pro/doctype/fp_team/fp_team.py @@ -1,12 +1,13 @@ # Copyright (c) 2025, harsh@buildwithhussain.com and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document -from frappe.utils import cached_property +from frappe.share import add_docshare from pydantic import BaseModel, EmailStr from forms_pro.api.user import get_user +from forms_pro.utils.teams import set_current_team class GetTeamMembersResponse(BaseModel): @@ -30,7 +31,7 @@ class FPTeam(Document): users: DF.TableMultiSelect[FPTeamMember] # end: auto-generated types - @cached_property + @property def team_members(self) -> list[GetTeamMembersResponse]: """ Get the list of team members @@ -60,4 +61,36 @@ def is_team_member(self, user: str) -> bool: Returns: bool - True if the user is a member of the team, False otherwise """ - return user in self.team_members + return user in [member["email"] for member in self.team_members] + + def after_insert(self) -> None: + self.add_to_team(self.owner) + set_current_team(self.name, self.owner) + self.save() + + def add_to_team(self, user: str) -> None: + """ + Add a user to the team + + Args: + user: The user email address + """ + if user == "Administrator": + return + + if self.is_team_member(user): + frappe.throw( + frappe._("User is already a member of the team"), + frappe.DuplicateEntryError, + ) + + self.append("users", {"user": user}) + add_docshare( + self.doctype, + self.name, + user, + read=1, + write=1, + share=1, + flags={"ignore_share_permission": True}, + ) diff --git a/forms_pro/forms_pro/doctype/fp_team/test_fp_team.py b/forms_pro/forms_pro/doctype/fp_team/test_fp_team.py index e353069..20caaad 100644 --- a/forms_pro/forms_pro/doctype/fp_team/test_fp_team.py +++ b/forms_pro/forms_pro/doctype/fp_team/test_fp_team.py @@ -1,9 +1,12 @@ # Copyright (c) 2025, harsh@buildwithhussain.com and Contributors # See license.txt -# import frappe +import frappe +from frappe.defaults import get_user_default from frappe.tests import IntegrationTestCase +from forms_pro.tests import FORMS_PRO_TEST_USER + # On IntegrationTestCase, the doctype test records and all # link-field test record dependencies are recursively loaded # Use these module variables to add/remove to/from that list @@ -17,4 +20,58 @@ class IntegrationTestFPTeam(IntegrationTestCase): Use this class for testing interactions between multiple components. """ - pass + def setUp(self): + super().setUp() + self.test_user = FORMS_PRO_TEST_USER + + def tearDown(self): + frappe.set_user("Administrator") + super().tearDown() + + def test_add_owner_to_team(self): + """ + Test that after a user creates a team, that owner user is added to the team and the team is shared with the owner user + """ + + frappe.set_user(self.test_user) + + team = frappe.new_doc("FP Team") + team.team_name = "Test Team" + team.insert() + team.reload() + + frappe.set_user("Administrator") + + # Check that the user is added to the team + self.assertTrue(team.is_team_member(self.test_user)) + + # Check that the user is added to the team + self.assertIsNotNone( + frappe.db.exists( + "FP Team Member", + { + "parent": team.name, + "parentfield": "users", + "parenttype": "FP Team", + "user": self.test_user, + }, + ) + ) + + # Check that the user is added to the team's docshare + self.assertTrue( + frappe.db.exists( + "DocShare", + { + "share_doctype": "FP Team", + "share_name": team.name, + "user": self.test_user, + "read": 1, + "write": 1, + "share": 1, + }, + ) + ) + + # Check that the user's current team is set to the team + self.assertEqual(get_user_default("current_team", self.test_user), team.name) diff --git a/forms_pro/install.py b/forms_pro/install.py index e719601..f408d42 100644 --- a/forms_pro/install.py +++ b/forms_pro/install.py @@ -2,6 +2,7 @@ from frappe.core.doctype.user.user import User from forms_pro.roles import FORMS_PRO_ROLE +from forms_pro.tests import FORMS_PRO_TEST_USER def after_install(): @@ -30,13 +31,11 @@ def give_admin_forms_pro_role(): def create_test_user(): - test_user = "test_forms_pro_user@example.com" - - if frappe.db.exists("User", test_user): + if frappe.db.exists("User", FORMS_PRO_TEST_USER): return user: User = frappe.new_doc("User") - user.email = test_user + user.email = FORMS_PRO_TEST_USER user.first_name = "Test" user.last_name = "Forms Pro User" user.insert(ignore_permissions=True) diff --git a/forms_pro/overrides/roles.py b/forms_pro/overrides/roles.py index 8655789..fd73088 100644 --- a/forms_pro/overrides/roles.py +++ b/forms_pro/overrides/roles.py @@ -2,7 +2,7 @@ from frappe.core.doctype.user.user import User from forms_pro.roles import FORMS_PRO_ROLE -from forms_pro.utils.teams import get_user_teams, set_current_team +from forms_pro.utils.teams import get_user_teams def has_forms_pro_permission() -> bool: @@ -48,4 +48,3 @@ def create_default_team_for_user(user: User) -> None: }, ) team.save(ignore_permissions=True) - set_current_team(team.name, user.name) diff --git a/forms_pro/public/images/logo_300.svg b/forms_pro/public/images/logo_300.svg new file mode 100644 index 0000000..de2b2ed --- /dev/null +++ b/forms_pro/public/images/logo_300.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/forms_pro/tests/__init__.py b/forms_pro/tests/__init__.py new file mode 100644 index 0000000..1f90860 --- /dev/null +++ b/forms_pro/tests/__init__.py @@ -0,0 +1,2 @@ +# Test user email address +FORMS_PRO_TEST_USER = "test_forms_pro_user@example.com" diff --git a/frontend/src/components/team/CreateTeamDialog.vue b/frontend/src/components/team/CreateTeamDialog.vue new file mode 100644 index 0000000..f02e8ea --- /dev/null +++ b/frontend/src/components/team/CreateTeamDialog.vue @@ -0,0 +1,77 @@ + + diff --git a/frontend/src/components/ui/Avatar.vue b/frontend/src/components/ui/Avatar.vue index 4160d04..700cb25 100644 --- a/frontend/src/components/ui/Avatar.vue +++ b/frontend/src/components/ui/Avatar.vue @@ -11,11 +11,13 @@ const props = withDefaults( variant?: "default" | "outline" | "filled"; shape?: "circle" | "square"; className?: string; + disabled?: boolean; }>(), { size: "md", variant: "default", shape: "circle", + disabled: false, } ); @@ -29,11 +31,16 @@ onMounted(async () => {