forked from beba/foerderbarometer
75 lines
3.0 KiB
Python
75 lines
3.0 KiB
Python
|
|
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()
|