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()