Initial commit from ZIP
This commit is contained in:
commit
378d4692a9
454 files changed
+40367
No files matched your search
+19
@@ -0,0 +1,19 @@
|
||||
from .packaging_type import PackagingType
|
||||
from .product_stage import ProductStage
|
||||
from .brief import Brief
|
||||
from .brief_config import BriefConfig
|
||||
|
||||
from .draft import Draft
|
||||
from .first_final import FirstFinal
|
||||
from .printing_files import PrintingFiles
|
||||
|
||||
from .product import Product
|
||||
|
||||
from .blanking import Blanking
|
||||
from .photo import Photo
|
||||
from .design_stage_file import (
|
||||
DesignStageFile,
|
||||
DraftFile,
|
||||
FirstFinalFile,
|
||||
PrintingFilesFile,
|
||||
)
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import os
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rules import always_allow
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.products import rules
|
||||
|
||||
|
||||
def product_blanking_filename(instance, filename):
|
||||
return f"projects/{instance.brief.product.project.slug[1:]}/blankings/{filename}"
|
||||
|
||||
|
||||
class Blanking(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"delete": rules.can_delete_blanking,
|
||||
"view": always_allow,
|
||||
}
|
||||
verbose_name = _("blanking")
|
||||
verbose_name_plural = _("blankings")
|
||||
|
||||
brief = models.ForeignKey(
|
||||
verbose_name=_("brief"),
|
||||
to="Brief",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="blankings",
|
||||
related_query_name="blanking",
|
||||
)
|
||||
|
||||
file = models.FileField(verbose_name=_("file"), upload_to=product_blanking_filename)
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return os.path.basename(self.file.name)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self.file.size
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.products import rules
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from outbo.apps.authentication.models import User
|
||||
|
||||
|
||||
class Brief(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"add": rules.can_create_brief,
|
||||
"delete": rules.can_delete_brief,
|
||||
"change": rules.can_update_brief,
|
||||
"view": rules.can_read_brief,
|
||||
}
|
||||
verbose_name = _("brief")
|
||||
verbose_name_plural = _("briefs")
|
||||
|
||||
product = models.OneToOneField(
|
||||
verbose_name=_("product"),
|
||||
to="Product",
|
||||
on_delete=models.CASCADE,
|
||||
primary_key=True,
|
||||
editable=False,
|
||||
related_name="brief",
|
||||
related_query_name="brief",
|
||||
)
|
||||
name_pol = models.CharField(verbose_name=_("product name (PL)"), max_length=200, blank=True, null=False)
|
||||
name_eng = models.CharField(verbose_name=_("product name (EN)"), max_length=200, blank=True, null=False)
|
||||
model_no = models.CharField(verbose_name=_("model number"), max_length=200, blank=True, null=False)
|
||||
batch_no = models.CharField(verbose_name=_("batch number"), max_length=200, blank=True, null=False)
|
||||
ean = models.CharField(verbose_name=_("EAN"), max_length=13, blank=True, null=False)
|
||||
packaging_type = models.ForeignKey(
|
||||
verbose_name=_("packaging type"),
|
||||
to="PackagingType",
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="+",
|
||||
related_query_name="+",
|
||||
)
|
||||
main_description = models.TextField(verbose_name=_("main description"), blank=True, null=False)
|
||||
extra_description = models.TextField(verbose_name=_("extra description"), blank=True, null=False)
|
||||
certificate = models.TextField(verbose_name=_("certificate"), blank=True, null=False)
|
||||
information_signs = models.TextField(verbose_name=_("information signs"), blank=True, null=False)
|
||||
capabilities_icons = models.TextField(verbose_name=_("capabilities icons"), blank=True, null=False)
|
||||
product_icons = models.TextField(verbose_name=_("product icons"), blank=True, null=False)
|
||||
has_description_eng = models.BooleanField(verbose_name=_("description (EN)"), default=False, null=False)
|
||||
has_description_pol = models.BooleanField(verbose_name=_("description (PL)"), default=False, null=False)
|
||||
has_description_por = models.BooleanField(verbose_name=_("description (PT)"), default=False, null=False)
|
||||
has_description_rus = models.BooleanField(verbose_name=_("description (RU)"), default=False, null=False)
|
||||
has_description_spa = models.BooleanField(verbose_name=_("description (ES)"), default=False, null=False)
|
||||
illustration = models.TextField(verbose_name=_("illustration"), blank=True, null=False)
|
||||
cleaning_method = models.TextField(verbose_name=_("cleaning method"), blank=True, null=False)
|
||||
country_of_origin = models.TextField(verbose_name=_("country of origin"), blank=True, null=False)
|
||||
manual = models.TextField(verbose_name=_("manual"), blank=True, null=False)
|
||||
material = models.TextField(verbose_name=_("material"), blank=True, null=False)
|
||||
warning = models.TextField(verbose_name=_("warning"), blank=True, null=False)
|
||||
arrangement_photo = models.TextField(verbose_name=_("arrangement photo"), blank=True, null=False)
|
||||
packshot = models.TextField(verbose_name=_("packshot"), blank=True, null=False)
|
||||
clipping = models.TextField(verbose_name=_("clippings"), blank=True, null=False)
|
||||
extra_photo = models.TextField(verbose_name=_("extra photo"), blank=True, null=False)
|
||||
|
||||
def __str__(self):
|
||||
return f"""{self.product.project.slug} / {self.name_pol or "<no name>"}"""
|
||||
|
||||
# serializer methods
|
||||
|
||||
def editable_for_user(self, *, user: "User") -> bool:
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
return user.is_supplier and self.product.stage.label == "1"
|
||||
|
||||
def acceptable_for_user(self, *, user: "User") -> bool:
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
return (user.is_agency and self.product.stage.pk == "waiting_for_brief_accept_by_agency") or (
|
||||
user.is_client and self.product.stage.pk == "waiting_for_brief_accept_by_client"
|
||||
)
|
||||
|
||||
def publishable_for_user(self, *, user: "User") -> bool:
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
return self.product.project.published and self.product.has_brief_publishable_for_user(user=user)
|
||||
@@ -0,0 +1,60 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
|
||||
|
||||
class BriefConfig(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
verbose_name = _("brief configuration")
|
||||
verbose_name_plural = _("brief configurations")
|
||||
|
||||
brief = models.OneToOneField(
|
||||
to="Brief",
|
||||
on_delete=models.CASCADE,
|
||||
primary_key=True,
|
||||
editable=False,
|
||||
related_name="config",
|
||||
related_query_name="config",
|
||||
)
|
||||
illustration = models.BooleanField(
|
||||
verbose_name=_("illustrations"), default=False, null=False
|
||||
)
|
||||
cleaning_method = models.BooleanField(
|
||||
verbose_name=_("cleaning method"), default=False, null=False
|
||||
)
|
||||
country_of_origin = models.BooleanField(
|
||||
verbose_name=_("country of origin"), default=False, null=False
|
||||
)
|
||||
manual = models.BooleanField(verbose_name=_("manual"), default=False, null=False)
|
||||
material = models.BooleanField(
|
||||
verbose_name=_("material"), default=False, null=False
|
||||
)
|
||||
warning = models.BooleanField(verbose_name=_("warnings"), default=False, null=False)
|
||||
photobrief = models.BooleanField(
|
||||
verbose_name=_("photobrief"), default=False, null=False
|
||||
)
|
||||
packshot = models.BooleanField(
|
||||
verbose_name=_("packshot"), default=False, null=False
|
||||
)
|
||||
clipping = models.BooleanField(
|
||||
verbose_name=_("clipping"), default=False, null=False
|
||||
)
|
||||
extra_photo = models.BooleanField(
|
||||
verbose_name=_("extra photo"), default=False, null=False
|
||||
)
|
||||
|
||||
def save(
|
||||
self, force_insert=False, force_update=False, using=None, update_fields=None
|
||||
):
|
||||
result = super().save(
|
||||
force_insert=force_insert,
|
||||
force_update=force_update,
|
||||
using=using,
|
||||
update_fields=update_fields,
|
||||
)
|
||||
if self.brief:
|
||||
self.brief.save(using=using)
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,190 @@
|
||||
import os
|
||||
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.db import models
|
||||
from django.db.models import F
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.text import slugify
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from text_unidecode import unidecode
|
||||
|
||||
from outbo.apps.core.models import (
|
||||
TimestampableDeletableMixin,
|
||||
RulesPolymorphicModel,
|
||||
SoftDeletablePolymorphicManager,
|
||||
)
|
||||
from outbo.apps.core.rules import is_agency
|
||||
from outbo.apps.products import rules
|
||||
|
||||
|
||||
@deconstructible
|
||||
class FormatFilename:
|
||||
def __init__(self, parent_name: str, filename_prefix: str, dir_name: str):
|
||||
self.parent_name = parent_name
|
||||
self.filename_prefix = filename_prefix
|
||||
self.dir_name = dir_name
|
||||
|
||||
def __call__(self, instance, filename):
|
||||
obj = getattr(instance, self.parent_name)
|
||||
filename = "-".join(
|
||||
(
|
||||
self.filename_prefix,
|
||||
obj.product.brief.model_no,
|
||||
slugify(unidecode(obj.product.brief.name_pol)),
|
||||
instance.version,
|
||||
)
|
||||
)
|
||||
return "/".join(
|
||||
(
|
||||
"projects",
|
||||
obj.product.project.slug[1:],
|
||||
self.dir_name,
|
||||
f"{filename}.pdf",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class DesignStageFile(TimestampableDeletableMixin, RulesPolymorphicModel):
|
||||
objects = SoftDeletablePolymorphicManager()
|
||||
all_objects = SoftDeletablePolymorphicManager(alive_only=False)
|
||||
|
||||
created_by = models.ForeignKey(
|
||||
verbose_name=_("uploaded by"),
|
||||
to="authentication.User",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="+",
|
||||
)
|
||||
version = models.CharField(verbose_name=_("version"), max_length=3, editable=False)
|
||||
description = models.TextField()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
parent_name = kwargs.pop("parent_name", None)
|
||||
parent = getattr(self, parent_name)
|
||||
|
||||
if self._state.adding is True and parent_name is not None:
|
||||
if is_agency.test(self.created_by):
|
||||
self.version = f"v{parent.max_version}"
|
||||
parent.max_version = F("max_version") + 1
|
||||
parent.save()
|
||||
else:
|
||||
self.version = "P"
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class DraftFile(DesignStageFile):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"view": rules.can_read_draft_file,
|
||||
"add": rules.can_create_draft_file,
|
||||
"change": rules.can_update_draft_file,
|
||||
"delete": rules.can_delete_draft_file,
|
||||
}
|
||||
verbose_name = _("draft file")
|
||||
verbose_name_plural = _("draft files")
|
||||
ordering = ["-created_at"]
|
||||
|
||||
draft = models.ForeignKey(
|
||||
verbose_name=_("draft"),
|
||||
to="Draft",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="files",
|
||||
related_query_name="file",
|
||||
)
|
||||
file = models.FileField(
|
||||
verbose_name=_("file"),
|
||||
upload_to=FormatFilename(
|
||||
filename_prefix="DRAFT", dir_name="drafts", parent_name="draft"
|
||||
),
|
||||
)
|
||||
opened_by = ArrayField(models.IntegerField(), null=True, blank=True)
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return os.path.basename(self.file.name)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self.file.size
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
return super().save(*args, **kwargs, parent_name="draft")
|
||||
|
||||
|
||||
class FirstFinalFile(DesignStageFile):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"view": rules.can_read_ff_file,
|
||||
"add": rules.can_create_ff_file,
|
||||
"change": rules.can_update_ff_file,
|
||||
"delete": rules.can_delete_ff_file,
|
||||
}
|
||||
verbose_name = _("first final file")
|
||||
verbose_name_plural = _("first final files")
|
||||
ordering = ["-created_at"]
|
||||
|
||||
first_final = models.ForeignKey(
|
||||
verbose_name=_("first final"),
|
||||
to="FirstFinal",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="files",
|
||||
related_query_name="file",
|
||||
)
|
||||
file = models.FileField(
|
||||
verbose_name=_("file"),
|
||||
upload_to=FormatFilename(
|
||||
filename_prefix="FF", dir_name="first_finals", parent_name="first_final"
|
||||
),
|
||||
)
|
||||
opened_by = ArrayField(models.IntegerField(), null=True, blank=True)
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return os.path.basename(self.file.name)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self.file.size
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
return super().save(*args, **kwargs, parent_name="first_final")
|
||||
|
||||
|
||||
class PrintingFilesFile(DesignStageFile):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"view": rules.can_read_pf_file,
|
||||
"add": rules.can_create_pf_file,
|
||||
"change": rules.can_update_pf_file,
|
||||
"delete": rules.can_delete_pf_file,
|
||||
}
|
||||
verbose_name = _("printing file")
|
||||
verbose_name_plural = _("printing files")
|
||||
ordering = ["-created_at"]
|
||||
|
||||
printing_files = models.ForeignKey(
|
||||
verbose_name=_("printing files"),
|
||||
to="PrintingFiles",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="files",
|
||||
related_query_name="file",
|
||||
)
|
||||
file = models.FileField(
|
||||
verbose_name=_("file"),
|
||||
upload_to=FormatFilename(
|
||||
filename_prefix="PF",
|
||||
dir_name="printing_files",
|
||||
parent_name="printing_files",
|
||||
),
|
||||
)
|
||||
opened_by = ArrayField(models.IntegerField(), null=True, blank=True)
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return os.path.basename(self.file.name)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self.file.size
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
return super().save(*args, **kwargs, parent_name="printing_files")
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.reverse import reverse_lazy, reverse
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.products import rules
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from outbo.apps.authentication.models import User
|
||||
|
||||
|
||||
class Draft(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"add": rules.can_create_draft,
|
||||
"delete": rules.can_delete_draft,
|
||||
"change": rules.can_update_draft,
|
||||
"view": rules.can_read_draft,
|
||||
}
|
||||
verbose_name = _("draft")
|
||||
verbose_name_plural = _("drafts")
|
||||
|
||||
product = models.OneToOneField(
|
||||
verbose_name=_("product"),
|
||||
to="Product",
|
||||
on_delete=models.CASCADE,
|
||||
primary_key=True,
|
||||
editable=False,
|
||||
related_name="draft",
|
||||
related_query_name="draft",
|
||||
)
|
||||
|
||||
# internals
|
||||
|
||||
max_version = models.PositiveSmallIntegerField(default=1)
|
||||
|
||||
def editable_for_user(self, *, user: "User") -> bool:
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
if user.is_agency:
|
||||
# initial publish
|
||||
return self.product.stage.pk == "waiting_for_draft"
|
||||
elif user.is_supplier:
|
||||
# improvements from supplier
|
||||
return self.product.stage.pk == "waiting_for_draft_accept_by_supplier"
|
||||
if user.is_client:
|
||||
# improvements from agency
|
||||
return self.product.stage.pk in [
|
||||
"waiting_for_draft_accept_by_supplier",
|
||||
"waiting_for_draft_accept_by_client",
|
||||
]
|
||||
return False
|
||||
|
||||
def acceptable_for_user(self, *, user: "User") -> bool:
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
if user.is_supplier:
|
||||
return self.product.stage.pk == "waiting_for_draft_accept_by_supplier"
|
||||
if user.is_client:
|
||||
return self.product.stage.pk == "waiting_for_draft_accept_by_client"
|
||||
return False
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.products import rules
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from outbo.apps.authentication.models import User
|
||||
|
||||
|
||||
class FirstFinal(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"add": rules.can_create_ff,
|
||||
"delete": rules.can_delete_ff,
|
||||
"change": rules.can_update_ff,
|
||||
"view": rules.can_read_ff,
|
||||
}
|
||||
verbose_name = _("first final")
|
||||
verbose_name_plural = _("first finals")
|
||||
|
||||
product = models.OneToOneField(
|
||||
verbose_name=_("product"),
|
||||
to="Product",
|
||||
on_delete=models.CASCADE,
|
||||
primary_key=True,
|
||||
editable=False,
|
||||
related_name="first_final",
|
||||
related_query_name="first_final",
|
||||
)
|
||||
|
||||
# internals
|
||||
|
||||
max_version = models.PositiveSmallIntegerField(default=1)
|
||||
|
||||
# serializer methods
|
||||
|
||||
def editable_for_user(self, user: "User"):
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
if user.is_agency:
|
||||
# initial publish
|
||||
return self.product.stage.pk == "waiting_for_ff"
|
||||
elif user.is_supplier:
|
||||
# improvements from supplier
|
||||
return (
|
||||
self.product.stage.pk == "waiting_for_ff" and self.files.count() > 0
|
||||
) or self.product.stage.pk == "waiting_for_ff_accept_by_supplier"
|
||||
if user.is_client:
|
||||
# improvements from agency
|
||||
return (
|
||||
self.product.stage.pk == "waiting_for_ff" and self.files.count() > 0
|
||||
) or self.product.stage.pk in [
|
||||
"waiting_for_ff_accept_by_supplier",
|
||||
"waiting_for_ff_accept_by_client",
|
||||
]
|
||||
return False
|
||||
|
||||
def acceptable_for_user(self, user: "User"):
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
if user.is_supplier:
|
||||
return self.product.stage.pk == "waiting_for_ff_accept_by_supplier"
|
||||
if user.is_client:
|
||||
return self.product.stage.pk == "waiting_for_ff_accept_by_client"
|
||||
return False
|
||||
@@ -0,0 +1,18 @@
|
||||
from django.db import models
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
|
||||
|
||||
class PackagingType(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
verbose_name = _("packaging type")
|
||||
verbose_name_plural = _("packaging types")
|
||||
ordering = ["name"]
|
||||
|
||||
name = models.CharField(verbose_name=_("name"), max_length=200)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rules import always_allow
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.products import rules
|
||||
|
||||
|
||||
class Photo(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {"view": always_allow, "delete": rules.can_delete_photo}
|
||||
|
||||
brief = models.ForeignKey(
|
||||
verbose_name=_("brief"),
|
||||
to="Brief",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="photos",
|
||||
related_query_name="photo",
|
||||
)
|
||||
|
||||
def product_photo_filename(self, filename):
|
||||
return f"projects/{self.brief.product.project.slug[1:]}/photos/{filename}"
|
||||
|
||||
file = models.ImageField(verbose_name=_("file"), upload_to=product_photo_filename)
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return os.path.basename(self.file.name)
|
||||
@@ -0,0 +1,69 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.products import rules
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from outbo.apps.authentication.models import User
|
||||
|
||||
|
||||
class PrintingFiles(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"add": rules.can_create_pf,
|
||||
"delete": rules.can_delete_pf,
|
||||
"change": rules.can_update_pf,
|
||||
"view": rules.can_read_pf,
|
||||
}
|
||||
verbose_name = _("printing files")
|
||||
verbose_name_plural = _("printing files")
|
||||
|
||||
product = models.OneToOneField(
|
||||
verbose_name=_("product"),
|
||||
to="Product",
|
||||
on_delete=models.CASCADE,
|
||||
primary_key=True,
|
||||
editable=False,
|
||||
related_name="printing_files",
|
||||
related_query_name="printing_files",
|
||||
)
|
||||
|
||||
# internals
|
||||
|
||||
max_version = models.PositiveSmallIntegerField(default=1)
|
||||
|
||||
# serializer methods
|
||||
|
||||
def editable_for_user(self, user: "User"):
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
if user.is_agency:
|
||||
# initial publish
|
||||
return self.product.stage.pk == "waiting_for_pf"
|
||||
elif user.is_supplier:
|
||||
# improvements from supplier
|
||||
return (
|
||||
self.product.stage.pk == "waiting_for_pf" and self.files.count() > 0
|
||||
) or self.product.stage.pk == "waiting_for_pf_accept_by_supplier"
|
||||
if user.is_client:
|
||||
# improvements from agency
|
||||
return (
|
||||
self.product.stage.pk == "waiting_for_pf" and self.files.count() > 0
|
||||
) or self.product.stage.pk in [
|
||||
"waiting_for_pf_accept_by_supplier",
|
||||
"waiting_for_pf_accept_by_client",
|
||||
]
|
||||
return False
|
||||
|
||||
def acceptable_for_user(self, user: "User"):
|
||||
if user.is_observer or user.is_trader:
|
||||
return False
|
||||
if user.is_supplier:
|
||||
return self.product.stage.pk == "waiting_for_pf_accept_by_supplier"
|
||||
if user.is_client:
|
||||
return self.product.stage.pk == "waiting_for_pf_accept_by_client"
|
||||
return False
|
||||
+621
@@ -0,0 +1,621 @@
|
||||
from functools import reduce
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_fsm import transition, RETURN_VALUE
|
||||
from outbo.apps.activity_log.enums import MessageType
|
||||
from outbo.apps.activity_log.models import ProductLogItem
|
||||
from outbo.apps.authentication.models import User
|
||||
from outbo.apps.core.fields import FSMKeyField
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
from outbo.apps.photobriefing.models import Brief as PhotoBrief
|
||||
from outbo.apps.products import rules
|
||||
from outbo.apps.products.models import (
|
||||
ProductStage,
|
||||
Draft,
|
||||
Brief,
|
||||
BriefConfig,
|
||||
FirstFinal,
|
||||
PrintingFiles,
|
||||
)
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
|
||||
class Product(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
rules_permissions = {
|
||||
"retrieve": rules.can_create_product,
|
||||
"view": rules.can_read_product,
|
||||
"update": rules.can_update_product,
|
||||
"partial_update": rules.can_update_product,
|
||||
"delete": rules.can_delete_product,
|
||||
}
|
||||
|
||||
project = models.ForeignKey(
|
||||
verbose_name=_("project"),
|
||||
to="projects.Project",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="products",
|
||||
related_query_name="product",
|
||||
null=False,
|
||||
)
|
||||
stage = FSMKeyField(
|
||||
verbose_name=_("stage"),
|
||||
to="ProductStage",
|
||||
on_delete=models.CASCADE,
|
||||
default="waiting_for_brief",
|
||||
protected=True,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"""{self.brief}"""
|
||||
|
||||
@property
|
||||
def display_name(self):
|
||||
return (
|
||||
f"""{self.brief.model_no or "<no model number>"} / {self.brief.name_pol or "<no model name>"}"""
|
||||
)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
result = super().save(
|
||||
force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields,
|
||||
)
|
||||
if self.project:
|
||||
self.project.save(using=using)
|
||||
|
||||
def has_complete_brief(self):
|
||||
conditions = [
|
||||
self.brief.name_pol,
|
||||
self.brief.photos.count() > 0,
|
||||
self.brief.blankings.count() > 0,
|
||||
self.brief.name_eng,
|
||||
self.brief.model_no,
|
||||
self.brief.batch_no,
|
||||
self.brief.ean,
|
||||
self.brief.packaging_type,
|
||||
self.brief.main_description,
|
||||
self.brief.extra_description,
|
||||
self.brief.certificate,
|
||||
self.brief.information_signs,
|
||||
self.brief.capabilities_icons,
|
||||
self.brief.product_icons,
|
||||
]
|
||||
if self.brief.config.illustration:
|
||||
conditions += [self.brief.illustration]
|
||||
if self.brief.config.cleaning_method:
|
||||
conditions += [self.brief.cleaning_method]
|
||||
if self.brief.config.country_of_origin:
|
||||
conditions += [self.brief.country_of_origin]
|
||||
if self.brief.config.manual:
|
||||
conditions += [self.brief.manual]
|
||||
if self.brief.config.material:
|
||||
conditions += [self.brief.material]
|
||||
if self.brief.config.warning:
|
||||
conditions += [self.brief.warning]
|
||||
if self.brief.config.photobrief:
|
||||
conditions += [self.brief.arrangement_photo]
|
||||
if self.brief.config.packshot:
|
||||
conditions += [self.brief.packshot]
|
||||
if self.brief.config.clipping:
|
||||
conditions += [self.brief.clipping]
|
||||
if self.brief.config.extra_photo:
|
||||
conditions += [self.brief.extra_photo]
|
||||
|
||||
return reduce(lambda accumulator, current: accumulator and bool(current), conditions, True)
|
||||
|
||||
def has_brief_publishable_for_user(self, *, user: "User") -> bool:
|
||||
stage = ProductStage.objects.get(id="waiting_for_brief")
|
||||
return user.is_supplier and self.stage == stage and self.has_complete_brief()
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_brief", target="waiting_for_brief_accept_by_agency",
|
||||
)
|
||||
def publish_brief_as_user(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_brief")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Brief published",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
|
||||
def accept_brief_as_user(self, *, user: "User") -> str:
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
product_ids = [member.user.id for member in self.project.members.all()]
|
||||
traders = User.objects.filter(is_trader=True, category=self.project.category).exclude(
|
||||
id__in=product_ids
|
||||
)
|
||||
if self.stage.pk == "waiting_for_brief_accept_by_agency":
|
||||
self._accept_brief_as_agency(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and (u.is_client and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in traders and (u.is_client and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
)
|
||||
elif self.stage.pk == "waiting_for_brief_accept_by_client":
|
||||
self._accept_brief_as_client(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
@transition(
|
||||
field="stage",
|
||||
source="waiting_for_brief_accept_by_agency",
|
||||
target="waiting_for_brief_accept_by_client",
|
||||
)
|
||||
def _accept_brief_as_agency(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_brief_accept_by_agency")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Brief accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
return "waiting_for_brief_accept_by_client"
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_brief_accept_by_client", target="waiting_for_draft",
|
||||
)
|
||||
def _accept_brief_as_client(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_brief_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Brief accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
Draft.objects.create(product=self)
|
||||
if self.brief.config.photobrief:
|
||||
PhotoBrief.objects.create(product=self)
|
||||
return "waiting_for_draft"
|
||||
|
||||
def reject_brief_as_user(self, *, user: "User") -> None:
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
if self.stage.pk == "waiting_for_brief_accept_by_agency":
|
||||
self._reject_brief_as_agency(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_supplier,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
elif self.stage.pk == "waiting_for_brief_accept_by_client":
|
||||
self._reject_brief_as_client(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_supplier,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_brief_accept_by_agency", target="waiting_for_brief",
|
||||
)
|
||||
def _reject_brief_as_agency(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_brief_accept_by_agency")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Brief rejected",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_brief_accept_by_client", target="waiting_for_brief",
|
||||
)
|
||||
def _reject_brief_as_client(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_brief_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Brief rejected",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
|
||||
def publish_draft_file_as_user(self, user: "User"):
|
||||
if self.stage.pk == "waiting_for_draft":
|
||||
return self._publish_draft_file_as_agency(user=user)
|
||||
if self.stage.pk == "waiting_for_draft_accept_by_supplier":
|
||||
return self._publish_draft_file_as_supplier(user=user)
|
||||
elif self.stage.pk == "waiting_for_draft_accept_by_client":
|
||||
return self._publish_draft_file_as_client(user=user)
|
||||
raise NotImplementedError
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_draft", target="waiting_for_draft_accept_by_supplier",
|
||||
)
|
||||
def _publish_draft_file_as_agency(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_draft")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Draft published",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_supplier,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_draft_accept_by_supplier", target="waiting_for_draft",
|
||||
)
|
||||
def _publish_draft_file_as_supplier(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_draft_accept_by_supplier")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Draft rejected",
|
||||
message_type=MessageType.FAILURE,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_draft_accept_by_client", target="waiting_for_draft",
|
||||
)
|
||||
def _publish_draft_file_as_client(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_draft_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Draft rejected",
|
||||
message_type=MessageType.FAILURE,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
def accept_draft_as_user(self, *, user: "User") -> str:
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
if self.stage.id == "waiting_for_draft_accept_by_supplier":
|
||||
result = self._accept_draft_as_supplier(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and (u.is_client and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/draft",
|
||||
)
|
||||
return result
|
||||
elif self.stage.id == "waiting_for_draft_accept_by_client":
|
||||
result = self._accept_draft_as_client(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/draft",
|
||||
)
|
||||
return result
|
||||
raise NotImplementedError()
|
||||
|
||||
@transition(
|
||||
field="stage",
|
||||
source="waiting_for_draft_accept_by_supplier",
|
||||
target=RETURN_VALUE("waiting_for_draft_accept_by_client"),
|
||||
)
|
||||
def _accept_draft_as_supplier(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_draft_accept_by_supplier")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Draft accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
return "waiting_for_draft_accept_by_client"
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_draft_accept_by_client", target=RETURN_VALUE("waiting_for_ff"),
|
||||
)
|
||||
def _accept_draft_as_client(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_draft_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="Draft accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
FirstFinal.objects.create(product=self)
|
||||
return "waiting_for_ff"
|
||||
|
||||
##########################
|
||||
def publish_ff_file_as_user(self, user: "User"):
|
||||
if self.stage.pk == "waiting_for_ff":
|
||||
return self._publish_ff_file_as_agency(user=user)
|
||||
if self.stage.pk == "waiting_for_ff_accept_by_supplier":
|
||||
return self._publish_ff_file_as_supplier(user=user)
|
||||
elif self.stage.pk == "waiting_for_ff_accept_by_client":
|
||||
return self._publish_ff_file_as_client(user=user)
|
||||
raise NotImplementedError
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_ff", target="waiting_for_ff_accept_by_supplier",
|
||||
)
|
||||
def _publish_ff_file_as_agency(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_ff")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="FF published",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_supplier,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/ff",
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_ff_accept_by_supplier", target="waiting_for_ff",
|
||||
)
|
||||
def _publish_ff_file_as_supplier(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_ff_accept_by_supplier")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="FF rejected",
|
||||
message_type=MessageType.FAILURE,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_ff_accept_by_client", target="waiting_for_ff",
|
||||
)
|
||||
def _publish_ff_file_as_client(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_ff_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="FF rejected",
|
||||
message_type=MessageType.FAILURE,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
def accept_ff_as_user(self, *, user: "User") -> str:
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
if self.stage.id == "waiting_for_ff_accept_by_supplier":
|
||||
product_ids = [member.user.id for member in self.project.members.all()]
|
||||
traders = User.objects.filter(is_trader=True, category=self.project.category).exclude(
|
||||
id__in=product_ids
|
||||
)
|
||||
result = self._accept_ff_as_supplier(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and (u.is_client and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/ff",
|
||||
)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in traders and (u.is_client and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
)
|
||||
return result
|
||||
elif self.stage.id == "waiting_for_ff_accept_by_client":
|
||||
result = self._accept_ff_as_client(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and (u.is_agency and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/ff",
|
||||
)
|
||||
return result
|
||||
raise NotImplementedError()
|
||||
|
||||
@transition(
|
||||
field="stage",
|
||||
source="waiting_for_ff_accept_by_supplier",
|
||||
target=RETURN_VALUE("waiting_for_ff_accept_by_client"),
|
||||
)
|
||||
def _accept_ff_as_supplier(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_ff_accept_by_supplier")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="FF accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
return "waiting_for_ff_accept_by_client"
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_ff_accept_by_client", target=RETURN_VALUE("waiting_for_pf"),
|
||||
)
|
||||
def _accept_ff_as_client(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_ff_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="FF accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
PrintingFiles.objects.create(product=self)
|
||||
return "waiting_for_pf"
|
||||
|
||||
###########
|
||||
def publish_pf_file_as_user(self, user: "User"):
|
||||
if self.stage.pk == "waiting_for_pf":
|
||||
return self._publish_pf_file_as_agency(user=user)
|
||||
if self.stage.pk == "waiting_for_pf_accept_by_supplier":
|
||||
return self._publish_pf_file_as_supplier(user=user)
|
||||
elif self.stage.pk == "waiting_for_pf_accept_by_client":
|
||||
return self._publish_pf_file_as_client(user=user)
|
||||
raise NotImplementedError
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_pf", target="waiting_for_pf_accept_by_supplier",
|
||||
)
|
||||
def _publish_pf_file_as_agency(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_pf")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="PF published",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_supplier,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/pf",
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_pf_accept_by_supplier", target="waiting_for_pf",
|
||||
)
|
||||
def _publish_pf_file_as_supplier(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_pf_accept_by_supplier")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="PF rejected",
|
||||
message_type=MessageType.FAILURE,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_pf_accept_by_client", target="waiting_for_pf",
|
||||
)
|
||||
def _publish_pf_file_as_client(self, *, user: "User") -> None:
|
||||
stage = ProductStage.objects.get(id="waiting_for_pf_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="PF rejected",
|
||||
message_type=MessageType.FAILURE,
|
||||
stage=stage,
|
||||
)
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and u.is_agency,
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
reject=True,
|
||||
)
|
||||
|
||||
def accept_pf_as_user(self, *, user: "User") -> str:
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
if self.stage.id == "waiting_for_pf_accept_by_supplier":
|
||||
result = self._accept_pf_as_supplier(user=user)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users and (u.is_client and u.is_observer is False),
|
||||
action_url="https://outbo.pl/projects/{id}/products/{pid}/pf",
|
||||
)
|
||||
return result
|
||||
elif self.stage.id == "waiting_for_pf_accept_by_client":
|
||||
result = self._accept_pf_as_client(user=user)
|
||||
return result
|
||||
raise NotImplementedError()
|
||||
|
||||
@transition(
|
||||
field="stage",
|
||||
source="waiting_for_pf_accept_by_supplier",
|
||||
target=RETURN_VALUE("waiting_for_pf_accept_by_client"),
|
||||
)
|
||||
def _accept_pf_as_supplier(self, *, user: "User") -> str:
|
||||
stage = ProductStage.objects.get(id="waiting_for_pf_accept_by_supplier")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="PF accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
return "waiting_for_pf_accept_by_client"
|
||||
|
||||
@transition(
|
||||
field="stage", source="waiting_for_pf_accept_by_client", target=RETURN_VALUE("completed"),
|
||||
)
|
||||
def _accept_pf_as_client(self, *, user: "User") -> str:
|
||||
product_users = [member.user for member in self.project.members.all()]
|
||||
stage = ProductStage.objects.get(id="waiting_for_pf_accept_by_client")
|
||||
ProductLogItem.objects.create(
|
||||
product=self,
|
||||
created_by=user,
|
||||
message="PF accepted",
|
||||
message_type=MessageType.SUCCESS,
|
||||
stage=stage,
|
||||
)
|
||||
self.send_action_emails_for_users_passing_test(
|
||||
test=lambda u: u in product_users, action_url="https://outbo.pl/projects/{id}/products/{pid}",
|
||||
)
|
||||
return "completed"
|
||||
|
||||
def send_action_email_to(self, *, user: User, action_url: str, reject: bool):
|
||||
if reject is False:
|
||||
stage = self.stage.next
|
||||
else:
|
||||
stage = self.stage.reset_stage
|
||||
product_status = f"{stage.label} - {stage.description}"
|
||||
msg_html = render_to_string(
|
||||
"email/user_notification.html",
|
||||
{
|
||||
"project_slug": self.project.slug,
|
||||
"project_description": self.project.description,
|
||||
"product_model": self.brief.model_no,
|
||||
"product_name_pol": self.brief.name_pol,
|
||||
"product_status": product_status,
|
||||
"product_current_action": f"{stage.next_action}",
|
||||
"action_url": action_url.format(id=self.project_id, pid=self.id),
|
||||
},
|
||||
)
|
||||
msg_txt = strip_tags(msg_html)
|
||||
user.email_user(
|
||||
subject=f"{self.project.slug} / {product_status} / {self.brief.name_pol}",
|
||||
message=msg_txt,
|
||||
html_message=msg_html,
|
||||
)
|
||||
|
||||
def send_action_emails_for_users_passing_test(self, *, test, action_url, reject: bool = False):
|
||||
users = [*User.objects.all()]
|
||||
client_users = filter(test, users)
|
||||
for u in client_users:
|
||||
self.send_action_email_to(user=u, action_url=action_url, reject=reject)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Product)
|
||||
def create_brief(sender, instance: Product, created, **kwargs):
|
||||
if created:
|
||||
Brief.objects.get_or_create(product=instance)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Brief)
|
||||
def create_brief_config(sender, instance: Brief, created, **kwargs):
|
||||
if created:
|
||||
BriefConfig.objects.get_or_create(brief=instance)
|
||||
@@ -0,0 +1,41 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rules.contrib.models import RulesModel
|
||||
|
||||
from outbo.apps.core.models import TimestampableDeletableMixin
|
||||
|
||||
|
||||
class ProductStage(TimestampableDeletableMixin, RulesModel):
|
||||
class Meta:
|
||||
ordering = ["label"]
|
||||
|
||||
id = models.CharField(verbose_name=_("ID"), primary_key=True, max_length=50)
|
||||
label = models.CharField(verbose_name=_("label"), max_length=2)
|
||||
group = models.PositiveSmallIntegerField(verbose_name=_("group"), null=False)
|
||||
description = models.CharField(verbose_name=_("description"), max_length=254)
|
||||
next_action = models.CharField(verbose_name=_("next action"), max_length=254)
|
||||
order = models.PositiveSmallIntegerField(verbose_name=_("order"))
|
||||
|
||||
def __str__(self):
|
||||
return f"[{self.label}] {self.description}"
|
||||
|
||||
@property
|
||||
def next(self):
|
||||
try:
|
||||
return ProductStage.objects.get(order=self.order + 1)
|
||||
except Exception:
|
||||
return ProductStage.objects.order_by("-order").first()
|
||||
|
||||
@property
|
||||
def reset_stage(self):
|
||||
stage_map = {
|
||||
"waiting_for_brief_accept_by_agency": "waiting_for_brief",
|
||||
"waiting_for_brief_accept_by_client": "waiting_for_brief",
|
||||
"waiting_for_draft_accept_by_supplier": "waiting_for_draft",
|
||||
"waiting_for_draft_accept_by_client": "waiting_for_draft",
|
||||
"waiting_for_ff_accept_by_client": "waiting_for_ff",
|
||||
"waiting_for_ff_accept_by_supplier": "waiting_for_ff",
|
||||
"waiting_for_pf_accept_by_supplier": "waiting_for_pf",
|
||||
"waiting_for_pf_accept_by_client": "waiting_for_pf",
|
||||
}
|
||||
return ProductStage.objects.get(id=stage_map[self.id])
|
||||
Reference in new issue
Block a user