106 lines
4.8 KiB
Python
106 lines
4.8 KiB
Python
from typing import cast
|
|
import strawberry
|
|
from strawberry.types import Info
|
|
from core.graphql.pubsub import pubsub
|
|
from core.graphql.inputs.invoice import InvoiceInput, InvoiceUpdateInput
|
|
from core.graphql.types.invoice import InvoiceType
|
|
from core.models.invoice import Invoice
|
|
from core.models.enums import InvoiceChoices
|
|
from core.graphql.utils import create_object, update_object, delete_object
|
|
from core.services.events import publish_invoice_generated, publish_invoice_paid
|
|
from core.services.events import EventPublisher
|
|
from core.models.enums import EventTypeChoices
|
|
|
|
|
|
@strawberry.type
|
|
class Mutation:
|
|
@strawberry.mutation(description="Create a new invoice")
|
|
async def create_invoice(self, input: InvoiceInput, info: Info) -> InvoiceType:
|
|
# Exclude m2m id fields from model constructor
|
|
payload = {k: v for k, v in input.__dict__.items() if k not in {"project_ids", "revenue_ids"}}
|
|
m2m_data = {
|
|
"projects": input.project_ids,
|
|
"revenues": input.revenue_ids,
|
|
}
|
|
instance = await create_object(payload, Invoice, m2m_data)
|
|
await pubsub.publish("invoice_created", instance.id)
|
|
|
|
# Publish event for notifications (invoice creation = invoice generated)
|
|
profile = getattr(info.context.request, 'profile', None)
|
|
await publish_invoice_generated(
|
|
invoice_id=str(instance.id),
|
|
triggered_by=profile,
|
|
metadata={'customer_id': str(instance.customer_id), 'status': instance.status}
|
|
)
|
|
|
|
return cast(InvoiceType, instance)
|
|
|
|
@strawberry.mutation(description="Update an existing invoice")
|
|
async def update_invoice(self, input: InvoiceUpdateInput, info: Info) -> InvoiceType:
|
|
# Get old invoice to check for status changes
|
|
from channels.db import database_sync_to_async
|
|
old_invoice = await database_sync_to_async(Invoice.objects.get)(pk=input.id.node_id)
|
|
old_status = old_invoice.status
|
|
|
|
# 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 {"project_ids", "revenue_ids"}}
|
|
m2m_data = {
|
|
"projects": getattr(input, "project_ids", None),
|
|
"revenues": getattr(input, "revenue_ids", None),
|
|
}
|
|
instance = await update_object(payload, Invoice, m2m_data)
|
|
await pubsub.publish("invoice_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:
|
|
# Publish status change event
|
|
await EventPublisher.publish(
|
|
event_type=EventTypeChoices.INVOICE_SENT if input.status == InvoiceChoices.SENT else
|
|
EventTypeChoices.INVOICE_PAID if input.status == InvoiceChoices.PAID else
|
|
EventTypeChoices.INVOICE_OVERDUE if input.status == InvoiceChoices.OVERDUE else
|
|
EventTypeChoices.INVOICE_CANCELLED if input.status == InvoiceChoices.CANCELLED else None,
|
|
entity_type='Invoice',
|
|
entity_id=str(instance.id),
|
|
triggered_by=profile,
|
|
metadata={'old_status': old_status, 'new_status': instance.status, 'customer_id': str(instance.customer_id)}
|
|
)
|
|
|
|
# Special handling for paid invoices
|
|
if instance.status == InvoiceChoices.PAID:
|
|
await publish_invoice_paid(
|
|
invoice_id=str(instance.id),
|
|
triggered_by=profile,
|
|
metadata={'customer_id': str(instance.customer_id), 'amount': str(instance.amount)}
|
|
)
|
|
|
|
return cast(InvoiceType, instance)
|
|
|
|
@strawberry.mutation(description="Delete an existing invoice")
|
|
async def delete_invoice(self, id: strawberry.ID, info: Info) -> strawberry.ID:
|
|
# Get invoice before deletion to access customer_id for event
|
|
from channels.db import database_sync_to_async
|
|
from core.graphql.utils import _decode_global_id
|
|
pk = _decode_global_id(id)
|
|
invoice = await database_sync_to_async(Invoice.objects.get)(pk=pk)
|
|
customer_id = str(invoice.customer_id)
|
|
|
|
instance = await delete_object(id, Invoice)
|
|
if not instance:
|
|
raise ValueError(f"Invoice with ID {id} does not exist")
|
|
await pubsub.publish("invoice_deleted", id)
|
|
|
|
# Publish event for notifications (deletion treated as cancellation)
|
|
profile = getattr(info.context.request, 'profile', None)
|
|
await EventPublisher.publish(
|
|
event_type=EventTypeChoices.INVOICE_CANCELLED,
|
|
entity_type='Invoice',
|
|
entity_id=str(id),
|
|
triggered_by=profile,
|
|
metadata={'customer_id': customer_id, 'action': 'deleted'}
|
|
)
|
|
|
|
return id
|