diff --git a/input/services.py b/input/services.py new file mode 100644 index 0000000..b6b9c6d --- /dev/null +++ b/input/services.py @@ -0,0 +1,75 @@ +from datetime import date +from django.db import transaction +from .models import ProjectRequest, ProjectsDeclined, Project, Account + +def approve_project_request(request_id: int, decided_by: str, account_code: str) -> Project: + # Use a DB transaction so either all changes commit, or none (atomic workflow). + with transaction.atomic(): + # SELECT ... FOR UPDATE: lock the row to avoid concurrent approvals/declines. + req = ProjectRequest.objects.select_for_update().get(pk=request_id) + + # Mark the request as approved and persist the decision metadata. + req.decision = 'APPROVED' + req.decision_date = date.today() # For DateField a date is fine; prefer timezone.localdate() if TZ-sensitive. + req.decided_by = decided_by + req.save() + + # The Account (Kostenstelle) must be assigned by WMDE in the admin workflow. + # .get() will raise DoesNotExist/MultipleObjectsReturned if data integrity is broken. + account = Account.objects.get(code=account_code) + + # Create the actual Project from the request data. + # Project.save() will generate pid/finance_id/end_quartal according to your model logic. + proj = Project.objects.create( + realname=req.realname, + email=req.email, + end_mail_send=False, + name=req.name, + description=req.description, + start=req.start, + end=req.end, + page=req.page, + group=req.group, + location=req.location, + participants_estimated=req.participants_estimated, + insurance=req.insurance, + cost=req.cost, + account=account, + notes=req.notes, + granted=True, + granted_date=date.today(), # Consider timezone.localdate() if you care about time zones. + granted_from=decided_by, + ) + + # After successful creation we remove the original request (it has been fulfilled). + # Because we're inside an atomic block, both operations succeed/fail together. + req.delete() + + # Return the created project for further processing in the caller if needed. + return proj + + +def decline_project_request(request_id: int, reason: str | None = None): + # Same transactional guarantees for declines. + with transaction.atomic(): + # Lock the row to prevent concurrent decisions. + req = ProjectRequest.objects.select_for_update().get(pk=request_id) + + # Mark as declined and persist decision date. + req.decision = 'DECLINED' + req.decision_date = date.today() + req.save() + + # Archive minimal relevant information in a dedicated table. + # No pid/finance_id should be created for declined items. + ProjectsDeclined.objects.create( + original_request_id=req.id, + name=req.name, + realname=req.realname, + email=req.email, + decision_date=req.decision_date, + reason=reason or '', + ) + + # Remove the original request after archiving. + req.delete() \ No newline at end of file