|
16 | 16 | from files.models import UserFile |
17 | 17 | from industries.models import Industry |
18 | 18 | from projects.managers import AchievementManager, CollaboratorManager, ProjectManager |
| 19 | +from projects.validators import inn_validator |
19 | 20 | from users.models import CustomUser |
20 | 21 |
|
21 | 22 | User = get_user_model() |
@@ -170,6 +171,12 @@ class Project(models.Model): |
170 | 171 | User, verbose_name="Подписчики", related_name="subscribed_projects" |
171 | 172 | ) |
172 | 173 |
|
| 174 | + companies = models.ManyToManyField( |
| 175 | + "Company", |
| 176 | + through="ProjectCompany", |
| 177 | + related_name="projects", |
| 178 | + ) |
| 179 | + |
173 | 180 | datetime_created = models.DateTimeField( |
174 | 181 | verbose_name="Дата создания", null=False, auto_now_add=True |
175 | 182 | ) |
@@ -426,3 +433,114 @@ def __str__(self) -> str: |
426 | 433 | class Meta: |
427 | 434 | verbose_name = "Цель" |
428 | 435 | verbose_name_plural = "Цели" |
| 436 | + |
| 437 | + |
| 438 | +class Company(models.Model): |
| 439 | + name = models.CharField(max_length=255) |
| 440 | + inn = models.CharField(max_length=12, unique=True, validators=[inn_validator]) |
| 441 | + |
| 442 | + class Meta: |
| 443 | + verbose_name = "Компания" |
| 444 | + verbose_name_plural = "Компании" |
| 445 | + ordering = ["name"] |
| 446 | + |
| 447 | + def __str__(self): |
| 448 | + return f"{self.name} ({self.inn})" |
| 449 | + |
| 450 | + |
| 451 | +class ProjectCompany(models.Model): |
| 452 | + project = models.ForeignKey( |
| 453 | + "projects.Project", |
| 454 | + on_delete=models.CASCADE, |
| 455 | + related_name="project_companies", |
| 456 | + ) |
| 457 | + company = models.ForeignKey( |
| 458 | + Company, |
| 459 | + on_delete=models.CASCADE, |
| 460 | + related_name="project_links", |
| 461 | + ) |
| 462 | + contribution = models.TextField(blank=True) |
| 463 | + decision_maker = models.ForeignKey( |
| 464 | + User, |
| 465 | + on_delete=models.SET_NULL, |
| 466 | + null=True, |
| 467 | + blank=True, |
| 468 | + related_name="partner_decisions", |
| 469 | + ) |
| 470 | + |
| 471 | + class Meta: |
| 472 | + verbose_name = "Связь проекта и компании" |
| 473 | + verbose_name_plural = "Связи проекта и компании" |
| 474 | + constraints = [ |
| 475 | + models.UniqueConstraint( |
| 476 | + fields=["project", "company"], |
| 477 | + name="uq_project_company_unique_pair", |
| 478 | + ), |
| 479 | + ] |
| 480 | + |
| 481 | + def __str__(self): |
| 482 | + return f"{self.project} - {self.company}" |
| 483 | + |
| 484 | + |
| 485 | +class Resource(models.Model): |
| 486 | + class ResourceType(models.TextChoices): |
| 487 | + INFRASTRUCTURE = "infrastructure", "Инфраструктурный" |
| 488 | + STAFF = "staff", "Кадровый" |
| 489 | + FINANCIAL = "financial", "Финансовый" |
| 490 | + INFORMATION = "information", "Информационный" |
| 491 | + |
| 492 | + project = models.ForeignKey( |
| 493 | + "projects.Project", |
| 494 | + on_delete=models.CASCADE, |
| 495 | + related_name="resources", |
| 496 | + ) |
| 497 | + type = models.CharField( |
| 498 | + max_length=32, |
| 499 | + choices=ResourceType.choices, |
| 500 | + ) |
| 501 | + description = models.TextField() |
| 502 | + |
| 503 | + partner_company = models.ForeignKey( |
| 504 | + Company, |
| 505 | + on_delete=models.PROTECT, |
| 506 | + null=True, |
| 507 | + blank=True, |
| 508 | + related_name="resources", |
| 509 | + help_text="Если не указано — ресурс в поиске партнёра.", |
| 510 | + ) |
| 511 | + |
| 512 | + class Meta: |
| 513 | + verbose_name = "Ресурс" |
| 514 | + verbose_name_plural = "Ресурсы" |
| 515 | + ordering = ["project", "type", "id"] |
| 516 | + |
| 517 | + def __str__(self): |
| 518 | + base = f"{self.get_type_display()} ресурс для {self.project}" |
| 519 | + return f"{base} — {self.partner_display}" |
| 520 | + |
| 521 | + @property |
| 522 | + def partner_display(self): |
| 523 | + return ( |
| 524 | + self.partner_company.name |
| 525 | + if self.partner_company |
| 526 | + else "в поиске партнёра для данного ресурса" |
| 527 | + ) |
| 528 | + |
| 529 | + def clean(self): |
| 530 | + """ |
| 531 | + Проверяет, что выбранная partner_company действительно является партнёром проекта. |
| 532 | + """ |
| 533 | + super().clean() |
| 534 | + if self.partner_company: |
| 535 | + exists = ProjectCompany.objects.filter( |
| 536 | + project=self.project, company=self.partner_company |
| 537 | + ).exists() |
| 538 | + if not exists: |
| 539 | + raise ValidationError( |
| 540 | + { |
| 541 | + "partner_company": ( |
| 542 | + "Эта компания не является партнёром данного проекта. " |
| 543 | + "Сначала добавьте её в партнёры проекта." |
| 544 | + ) |
| 545 | + } |
| 546 | + ) |
0 commit comments