189 lines
7.8 KiB
Python
189 lines
7.8 KiB
Python
from typing import cast
|
|
import strawberry
|
|
from strawberry.types import Info
|
|
from asgiref.sync import sync_to_async
|
|
from channels.db import database_sync_to_async
|
|
from core.graphql.pubsub import pubsub
|
|
from core.graphql.inputs.project import ProjectInput, ProjectUpdateInput
|
|
from core.graphql.types.project import ProjectType
|
|
from core.models.account import AccountAddress
|
|
from core.models.profile import TeamProfile
|
|
from core.models.project import Project
|
|
from core.models.enums import ServiceChoices
|
|
from core.graphql.utils import create_object, update_object, delete_object
|
|
from core.services.events import (
|
|
publish_project_created, publish_project_status_changed,
|
|
publish_project_completed, publish_project_dispatched,
|
|
publish_project_deleted,
|
|
)
|
|
|
|
|
|
# Helper to get admin profile
|
|
async def _get_admin_profile():
|
|
return await sync_to_async(
|
|
lambda: TeamProfile.objects.filter(role='ADMIN').first()
|
|
)()
|
|
|
|
|
|
# Helper to check if admin is in team member IDs (handles GlobalID objects)
|
|
def _admin_in_team_members(admin_id, team_member_ids):
|
|
if not team_member_ids or not admin_id:
|
|
return False
|
|
# team_member_ids may be GlobalID objects with .node_id attribute
|
|
member_uuids = []
|
|
for mid in team_member_ids:
|
|
if hasattr(mid, 'node_id'):
|
|
member_uuids.append(str(mid.node_id))
|
|
else:
|
|
member_uuids.append(str(mid))
|
|
return str(admin_id) in member_uuids
|
|
|
|
|
|
# Helper to get old team member IDs from instance
|
|
async def _get_old_team_member_ids(instance):
|
|
return await sync_to_async(
|
|
lambda: set(str(m.id) for m in instance.team_members.all())
|
|
)()
|
|
|
|
|
|
@strawberry.type
|
|
class Mutation:
|
|
@strawberry.mutation(description="Create a new project")
|
|
async def create_project(self, input: ProjectInput, info: Info) -> ProjectType:
|
|
# Exclude m2m id fields from model constructor
|
|
payload = {k: v for k, v in input.__dict__.items() if k not in {"team_member_ids"}}
|
|
m2m_data = {"team_members": input.team_member_ids}
|
|
instance = await create_object(payload, Project, m2m_data)
|
|
await pubsub.publish("project_created", instance.id)
|
|
|
|
# Publish event for notifications
|
|
profile = getattr(info.context.request, 'profile', None)
|
|
await publish_project_created(
|
|
project_id=str(instance.id),
|
|
triggered_by=profile,
|
|
metadata={
|
|
'status': instance.status,
|
|
'customer_id': str(instance.customer_id),
|
|
'name': instance.name,
|
|
'date': str(instance.date)
|
|
}
|
|
)
|
|
|
|
# Check if project was dispatched (admin in team members)
|
|
admin = await _get_admin_profile()
|
|
if admin and _admin_in_team_members(admin.id, input.team_member_ids):
|
|
# Build metadata
|
|
account_address_id = None
|
|
account_name = None
|
|
if instance.account_address_id:
|
|
account_address_id = str(instance.account_address_id)
|
|
account_address = await sync_to_async(
|
|
lambda: AccountAddress.objects.select_related('account').get(id=instance.account_address_id)
|
|
)()
|
|
account_name = account_address.account.name if account_address.account else None
|
|
|
|
await publish_project_dispatched(
|
|
project_id=str(instance.id),
|
|
triggered_by=profile,
|
|
metadata={
|
|
'project_id': str(instance.id),
|
|
'project_name': instance.name,
|
|
'customer_id': str(instance.customer_id),
|
|
'account_address_id': account_address_id,
|
|
'account_name': account_name,
|
|
'date': str(instance.date),
|
|
'status': instance.status
|
|
}
|
|
)
|
|
|
|
return cast(ProjectType, instance)
|
|
|
|
@strawberry.mutation(description="Update an existing project")
|
|
async def update_project(self, input: ProjectUpdateInput, info: Info) -> ProjectType:
|
|
# Get old project to check for status changes
|
|
old_project = await database_sync_to_async(Project.objects.get)(pk=input.id.node_id)
|
|
old_status = old_project.status
|
|
|
|
# Get old team member IDs before update (for dispatched detection)
|
|
old_team_member_ids = await _get_old_team_member_ids(old_project)
|
|
|
|
# Keep id and non-m2m fields; drop m2m *_ids from the update payload
|
|
payload = {k: v for k, v in input.__dict__.items() if k not in {"team_member_ids"}}
|
|
m2m_data = {"team_members": getattr(input, "team_member_ids", None)}
|
|
instance = await update_object(payload, Project, m2m_data)
|
|
await pubsub.publish("project_updated", instance.id)
|
|
|
|
# Publish events for notifications
|
|
profile = getattr(info.context.request, 'profile', None)
|
|
|
|
# Check if status changed
|
|
if hasattr(input, 'status') and input.status and input.status != old_status:
|
|
await publish_project_status_changed(
|
|
project_id=str(instance.id),
|
|
old_status=old_status,
|
|
new_status=instance.status,
|
|
triggered_by=profile
|
|
)
|
|
|
|
# Check if project was completed
|
|
if instance.status == ServiceChoices.COMPLETED:
|
|
await publish_project_completed(
|
|
project_id=str(instance.id),
|
|
triggered_by=profile,
|
|
metadata={
|
|
'customer_id': str(instance.customer_id),
|
|
'name': instance.name,
|
|
'date': str(instance.date)
|
|
}
|
|
)
|
|
|
|
# Check if admin was newly added (dispatched)
|
|
if input.team_member_ids is not None:
|
|
admin = await _get_admin_profile()
|
|
if admin:
|
|
admin_was_in_old = str(admin.id) in old_team_member_ids
|
|
admin_in_new = _admin_in_team_members(admin.id, input.team_member_ids)
|
|
|
|
if not admin_was_in_old and admin_in_new:
|
|
# Admin was just added - project was dispatched
|
|
account_address_id = None
|
|
account_name = None
|
|
if instance.account_address_id:
|
|
account_address_id = str(instance.account_address_id)
|
|
account_address = await sync_to_async(
|
|
lambda: AccountAddress.objects.select_related('account').get(id=instance.account_address_id)
|
|
)()
|
|
account_name = account_address.account.name if account_address.account else None
|
|
|
|
await publish_project_dispatched(
|
|
project_id=str(instance.id),
|
|
triggered_by=profile,
|
|
metadata={
|
|
'project_id': str(instance.id),
|
|
'project_name': instance.name,
|
|
'customer_id': str(instance.customer_id),
|
|
'account_address_id': account_address_id,
|
|
'account_name': account_name,
|
|
'date': str(instance.date),
|
|
'status': instance.status
|
|
}
|
|
)
|
|
|
|
return cast(ProjectType, instance)
|
|
|
|
@strawberry.mutation(description="Delete an existing project")
|
|
async def delete_project(self, id: strawberry.ID, info: Info) -> strawberry.ID:
|
|
instance = await delete_object(id, Project)
|
|
if not instance:
|
|
raise ValueError(f"Project with ID {id} does not exist")
|
|
await pubsub.publish("project_deleted", id)
|
|
|
|
# Publish event for notifications
|
|
profile = getattr(info.context.request, 'profile', None)
|
|
await publish_project_deleted(
|
|
project_id=str(id),
|
|
triggered_by=profile
|
|
)
|
|
|
|
return id
|