feat(aufgaben): Verknüpfungen, implizite Fälligkeit, Issue-Facade (WP-0015)
- Aufgabe.erstellt_am für implizite 7-Tage-Fälligkeit - frist_effektiv property; ist_ueberfaellig nutzt sie - AufgabenVerknuepfung (GenericForeignKey) mit HTMX-Panel - ExternalIssue (OneToOne) mit IssueAdapter-ABC und HTMX-Panel - link_registry.py und issue_facade.py als zentrale Registries - 8 neue Tests, 76 gesamt grün Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a0c0ddf2eb
commit
816c281f6a
19 changed files with 713 additions and 19 deletions
|
|
@ -1,5 +1,7 @@
|
|||
from datetime import date
|
||||
from datetime import date, timedelta
|
||||
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
|
||||
from vergabe_teilnahme.apps.core.models import FlexibleModel
|
||||
|
|
@ -61,6 +63,7 @@ class Aufgabe(FlexibleModel):
|
|||
status = models.CharField(max_length=25, choices=STATUS_CHOICES, default='offen')
|
||||
prioritaet = models.PositiveSmallIntegerField(choices=PRIORITAET_CHOICES, default=2)
|
||||
frist = models.DateField(null=True, blank=True)
|
||||
erstellt_am = models.DateTimeField(auto_now_add=True)
|
||||
verantwortlicher = models.ForeignKey(
|
||||
'accounts.Mitarbeiter', on_delete=models.SET_NULL, null=True, blank=True
|
||||
)
|
||||
|
|
@ -75,11 +78,18 @@ class Aufgabe(FlexibleModel):
|
|||
def __str__(self):
|
||||
return self.titel
|
||||
|
||||
@property
|
||||
def frist_effektiv(self):
|
||||
if self.frist:
|
||||
return self.frist
|
||||
return (self.erstellt_am + timedelta(days=7)).date()
|
||||
|
||||
@property
|
||||
def ist_ueberfaellig(self):
|
||||
if not self.frist:
|
||||
return False
|
||||
return self.frist < date.today() and self.status not in ['erledigt', 'verworfen']
|
||||
return (
|
||||
self.frist_effektiv < date.today()
|
||||
and self.status not in ['erledigt', 'verworfen']
|
||||
)
|
||||
|
||||
|
||||
class Bieterfrage(FlexibleModel):
|
||||
|
|
@ -122,3 +132,58 @@ class Bieterfrage(FlexibleModel):
|
|||
|
||||
def __str__(self):
|
||||
return self.fragentext[:80]
|
||||
|
||||
|
||||
class AufgabenVerknuepfung(models.Model):
|
||||
aufgabe = models.ForeignKey(
|
||||
Aufgabe, on_delete=models.CASCADE, related_name='verknuepfungen'
|
||||
)
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
object_id = models.PositiveIntegerField()
|
||||
ziel = GenericForeignKey('content_type', 'object_id')
|
||||
kommentar = models.TextField(blank=True)
|
||||
erstellt_am = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['erstellt_am']
|
||||
verbose_name = 'Aufgaben-Verknüpfung'
|
||||
verbose_name_plural = 'Aufgaben-Verknüpfungen'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.aufgabe} → {self.content_type} #{self.object_id}'
|
||||
|
||||
|
||||
class ExternalIssue(models.Model):
|
||||
SYSTEM_CHOICES = [
|
||||
('github', 'GitHub Issues'),
|
||||
('jira', 'Jira'),
|
||||
('linear', 'Linear'),
|
||||
('azure', 'Azure DevOps'),
|
||||
('sonstiges', 'Sonstiges'),
|
||||
]
|
||||
SYNC_STATUS_CHOICES = [
|
||||
('manuell', 'Manuell'),
|
||||
('offen', 'Offen (extern)'),
|
||||
('geschlossen', 'Geschlossen (extern)'),
|
||||
('fehler', 'Sync-Fehler'),
|
||||
]
|
||||
|
||||
aufgabe = models.OneToOneField(
|
||||
Aufgabe, on_delete=models.CASCADE, related_name='external_issue'
|
||||
)
|
||||
system = models.CharField(max_length=20, choices=SYSTEM_CHOICES)
|
||||
issue_url = models.URLField(blank=True)
|
||||
issue_key = models.CharField(max_length=100, blank=True,
|
||||
help_text='z.B. "GH-42" oder "PROJ-1234"')
|
||||
sync_status = models.CharField(max_length=20, choices=SYNC_STATUS_CHOICES,
|
||||
default='manuell')
|
||||
letzter_sync = models.DateTimeField(null=True, blank=True)
|
||||
notizen = models.TextField(blank=True)
|
||||
erstellt_am = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'External Issue'
|
||||
verbose_name_plural = 'External Issues'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.get_system_display()} {self.issue_key or self.issue_url}'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue