# coding: utf-8

import logging
import uuid
from datetime import timedelta

import reversion
from django.conf import settings
from django.contrib.auth.models import AbstractUser, Group, Permission
from django.contrib.postgres import fields as pg_fields
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.db.models import Model, OneToOneField
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from reversion.models import Revision
from slugify import slugify

from procu.api import enums, managers, utils
from procu.soft_delete.models import ExistingOnlyManager, ForeignKey, SoftDelete
from procu.wf.fields import FormattedField

logger = logging.getLogger(__name__)


def gen_uuid():
    return str(uuid.uuid4())


def get_filename(instance, filename):
    instance.filename = filename
    return slugify(filename, regex_pattern=r'[^-a-z0-9\.]+')


DEFAULT_PERMISSIONS = ('create', 'retrieve', 'update', 'delete', 'list')


def reversion_register(**kwargs):
    """
    The decorator registers a model in djagno_reversion using default
    parameters from settings.
    """
    args = settings.REVERSION_DEFAULTS.copy()
    args.update(kwargs)
    return reversion.register(**args)


class PermissionMixin(Model):
    perm_models = None
    _perm_names = None

    def _all_permissions(self):
        model_names = self.perm_models or (self._meta.model_name,)

        perms = self._perms = (
            Permission.objects.filter(content_type__model__in=model_names)
            .values_list('content_type__app_label', 'codename')
            .order_by()
        )
        return {f'{app}.{name}' for app, name in perms}

    def permissions(self, user):
        perm_names = cache.get_or_set(
            f'model_perms_{self._meta.model_name}',
            self._all_permissions,
            timeout=3600,
        )
        return perm_names & user.get_all_permissions()

    class Meta:
        abstract = True
        default_permissions = DEFAULT_PERMISSIONS


class GroupMixin(models.Model):
    """
    Add the fields and methods necessary to support the Group and Permission
    models using the ModelBackend.
    """

    class Meta:
        abstract = True

    def _group_permissions(self):

        if self.is_staff:
            # Get permissions via UserGroup (populated by IDM)
            values = (
                UserGroup.objects.values_list(
                    'group__permissions__content_type__app_label',
                    'group__permissions__codename',
                )
                .distinct('group__permissions')
                .filter(username=self.username)
            )

        else:
            # Permission from the fixed group for external users
            values = Permission.objects.values_list(
                'content_type__app_label', 'codename'
            ).filter(group__name='group-agent')

        return {f'{app}.{perm}' for app, perm in values}

    def get_group_permissions(self, obj=None):

        if not hasattr(self, '_cache_group_perms'):
            key = ('perms', self.username)
            perms = cache.get_or_set(key, self._group_permissions, timeout=3600)
            setattr(self, '_cache_group_perms', perms)

        return self._cache_group_perms

    get_all_permissions = get_group_permissions

    def has_perm(self, perm, obj=None):
        return perm in self.get_all_permissions()

    def has_perms(self, perm_list, obj=None):
        all_perms = self.get_all_permissions()
        return all_perms.issuperset(perm_list)

    def has_module_perms(self, app_label):
        return frozenset()


class BumpUpdated(Model):
    def bump(self):
        self.updated_at = timezone.now()
        self.save(update_fields=('updated_at',))

    class Meta:
        abstract = True


# ------------------------------------------------------------------------------


@reversion_register()
class User(GroupMixin, PermissionMixin, SoftDelete, AbstractUser):

    email = models.EmailField(blank=True, verbose_name=_('MODEL_USER::EMAIL'))

    first_name = models.CharField(
        max_length=100, blank=True, verbose_name=_('MODEL_USER::FIRST_NAME')
    )
    last_name = models.CharField(
        max_length=100, blank=True, verbose_name=_('MODEL_USER::LAST_NAME')
    )
    supplier = ForeignKey(
        'Supplier',
        related_name='agents',
        null=True,
        on_delete=models.CASCADE,
        verbose_name=_('MODEL_USER::SUPPLIER'),
    )
    comment = models.TextField(
        blank=True, verbose_name=_('MODEL_USER::COMMENT')
    )
    sex = models.IntegerField(
        choices=((0, 'unknown'), (1, 'male'), (2, 'female')),
        default=0,
        verbose_name=_('MODEL_USER::SEX'),
    )
    language = models.CharField(
        max_length=7, blank=True, verbose_name=_('MODEL_USER::LANGUAGE')
    )
    info = pg_fields.JSONField(default=dict, verbose_name=_('MODEL_USER::INFO'))

    is_cold = models.BooleanField(
        default=False, verbose_name=_('MODEL_USER::IS_COLD')
    )

    is_subscribed_created = models.BooleanField(
        default=False, verbose_name=_('MODEL_USER::IS_SUBSCRIBED_CREATED')
    )

    sort_quotes_by_updated = models.BooleanField(
        default=False, verbose_name=_('MODEL_USER::SORT_QUOTES_BY_UPDATED')
    )

    signature = models.TextField(
        blank=True, null=True, verbose_name=_('MODEL_USER::SIGNATURE')
    )

    # --------------------------------------------------------------

    objects = managers.UserManager()

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)

    @property
    def full_name(self):
        full_name = (self.first_name + ' ' + self.last_name).strip()
        if not full_name:
            full_name = self.username + ('@' if self.is_staff else '')
        return full_name

    @property
    def full_signature(self):

        if self.signature is None:
            return '\n'.join(('С уважением,', self.full_name))
        else:
            return self.signature

    def delete(self, **kwargs):
        supplier = self.supplier

        if supplier:
            self.supplier = None
            self.save(update_fields=('supplier',))

        super().delete(**kwargs)

    def set_roles(self, *roles):

        UserGroup.objects.filter(username=self.username).delete()

        group_names = ('group-' + role for role in roles)
        groups = Group.objects.filter(name__in=group_names)

        for group in groups:
            UserGroup.objects.update_or_create(
                username=self.username, group=group
            )

        cache.delete(('perms', self.username))

    def save(self, *args, **kwargs):
        if not self.is_staff:
            self.username = self.email
        super().save(*args, **kwargs)


# ------------------------------------------------------------------------------


class UserGroup(Model):

    username = models.TextField()
    group = ForeignKey(Group, on_delete=models.CASCADE)

    class Meta(PermissionMixin.Meta):
        unique_together = ('username', 'group')


# ------------------------------------------------------------------------------


class Unit(PermissionMixin, SoftDelete, Model):

    name = models.TextField(verbose_name=_('MODEL_UNIT::NAME'))
    shortname = models.TextField(verbose_name=_('MODEL_UNIT::SHORTNAME'))

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)


@reversion_register(follow=('suppliers',))
class SupplierTag(PermissionMixin, Model):
    label = models.CharField(
        max_length=127, unique=True, verbose_name=_('MODEL_SUPPLIER_TAG::LABEL')
    )
    comment = models.TextField(
        verbose_name=_('MODEL_SUPPLIER_TAG::COMMENT'), blank=True
    )
    is_hidden = models.BooleanField(
        verbose_name=_('MODEL_SUPPLIER_TAG::IS_HIDDEN'), default=False
    )

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)


@reversion_register()
class Supplier(PermissionMixin, SoftDelete, Model):

    title = models.CharField(
        max_length=255, verbose_name=_('MODEL_SUPPLIER::TITLE')
    )
    legal_name = models.CharField(
        max_length=255, blank=True, verbose_name=_('MODEL_SUPPLIER::LEGAL_NAME')
    )
    has_contract = models.BooleanField(
        default=False, verbose_name=_('MODEL_SUPPLIER::HAS_CONTRACT')
    )
    payment_terms = models.TextField(
        blank=True, verbose_name=_('MODEL_SUPPLIER::TERMS')
    )
    vat_id = models.CharField(
        max_length=127, blank=True, verbose_name=_('MODEL_SUPPLIER::VAT_ID')
    )
    comment = models.TextField(
        blank=True, verbose_name=_('MODEL_SUPPLIER::COMMENT')
    )
    tags = models.ManyToManyField(
        SupplierTag,
        blank=True,
        related_name='suppliers',
        verbose_name=_('MODEL_SUPPLIER::TAGS'),
    )
    info = pg_fields.JSONField(
        default=dict, verbose_name=_('MODEL_SUPPLIER::INFO')
    )
    risk = models.IntegerField(
        choices=tuple(enums.RISK.keys.items()),
        default=enums.RISK.UNKNOWN,
        verbose_name=_('MODEL_SUPPLIER::RISK'),
    )
    risk_data = pg_fields.JSONField(
        default=dict, verbose_name=_('MODEL_SUPPLIER::RISK_DATA')
    )
    risk_updated_at = models.DateTimeField(
        null=True, verbose_name=_('MODEL_SUPPLIER::RISK_UPDATED_AT')
    )
    is_cold = models.BooleanField(
        default=False, verbose_name=_('MODEL_SUPPLIER::IS_COLD')
    )
    author = ForeignKey(
        User,
        editable=False,
        verbose_name=_('MODEL_SUPPLIER::AUTHOR'),
        related_name='+',
        on_delete=models.CASCADE,
        null=True,
    )
    can_pay_by_card = models.BooleanField(
        default=False, verbose_name=_('MODEL_SUPPLIER::CAN_PAY_BY_CARD')
    )

    terms = pg_fields.JSONField(
        default=dict, verbose_name=_('MODEL_SUPPLIER::TERMS')
    )
    secrets = models.TextField(
        blank=True, verbose_name=_('MODEL_SUPPLIER::SECRETS')
    )
    oracle_supplier = ForeignKey(
        'OracleSupplier',
        verbose_name=_('MODEL_SUPPLIER::ORACLE_SUPPLIER'),
        related_name='+',
        on_delete=models.CASCADE,
        null=True,
    )

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)


class SupplierWarning(PermissionMixin, SoftDelete, Model):
    supplier = ForeignKey(
        Supplier,
        editable=False,
        on_delete=models.CASCADE,
        related_name='warnings',
    )
    author = ForeignKey(
        User,
        editable=False,
        on_delete=models.CASCADE,
        related_name='supplier_warnings',
        verbose_name=_('MODEL_SUPPLIERWARNING::AUTHOR'),
    )
    message = models.TextField(verbose_name=_('MODEL_SUPPLIERWARNING::TEXT'))
    created_at = models.DateTimeField(
        verbose_name=_('MODEL_SUPPLIERWARNING::CREATED_AT'),
        default=timezone.now,
        editable=False,
    )
    updated_at = models.DateTimeField(
        verbose_name=_('MODEL_SUPPLIERWARNING::UPDATED_AT'),
        default=timezone.now,
        editable=False,
    )

    objects = managers.ExistingOnlyManager()
    with_deleted = models.Manager()

    class Meta(PermissionMixin.Meta):
        base_manager_name = 'objects'
        default_manager_name = 'with_deleted'

    def permissions(self, user):
        perms = super().permissions(user)

        if not (user == self.author):
            perms.discard('api.update_supplierwarning')
            perms.discard('api.delete_supplierwarning')

        return perms


class SparkCache(models.Model):

    vat_id = models.CharField(
        max_length=127, unique=True, verbose_name=_('MODEL_SPARKCACHE::VAT_ID')
    )
    risk_data = pg_fields.JSONField(
        default=dict, verbose_name=_('MODEL_SPARKCACHE::RISK_DATA')
    )
    updated_at = models.DateTimeField(
        default=timezone.now, verbose_name=_('MODEL_SPARKCACHE::UPDATED_AT')
    )


@reversion_register()
class Address(PermissionMixin, SoftDelete, Model):

    label = models.CharField(
        max_length=255, verbose_name=_('MODEL_ADDRESS::LABEL'), blank=True
    )
    text = models.TextField(verbose_name=_('MODEL_ADDRESS::TEXT'))
    tracker_location = models.CharField(
        max_length=31,
        verbose_name=_('MODEL_ADDRESS::TRACKER_LOCATION'),
        blank=True,
    )
    tracker_components = pg_fields.ArrayField(
        models.CharField(max_length=63),
        verbose_name=_('MODEL_ADDRESS::TRACKER_COMPONENTS'),
        default=list,
    )
    comment = models.TextField(
        verbose_name=_('MODEL_ADDRESS::COMMENT'), blank=True
    )
    contacts = pg_fields.JSONField(
        default=dict, verbose_name=_('MODEL_ADDRESS::CONTACTS')
    )
    geo = pg_fields.JSONField(
        default=None, null=True, verbose_name=_('MODEL_ADDRESS::GEO')
    )

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)


class Discount(PermissionMixin, Model):

    value = models.DecimalField(
        decimal_places=2, max_digits=5, verbose_name=_('MODEL_DISCOUNT::VALUE')
    )
    dates = pg_fields.DateRangeField(verbose_name=_('MODEL_DISCOUNT::DATES'))

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)


@reversion_register()
class Currency(PermissionMixin, SoftDelete, Model):

    code = models.CharField(
        max_length=6, blank=True, verbose_name=_('MODEL_CURRENCY::CODE')
    )
    char_code = models.CharField(
        max_length=4, blank=True, verbose_name=_('MODEL_CURRENCY::CHAR_CODE')
    )
    prefix = models.CharField(
        max_length=10, blank=True, verbose_name=_('MODEL_CURRENCY::PREFIX')
    )
    suffix = models.CharField(
        max_length=10, blank=True, verbose_name=_('MODEL_CURRENCY::SUFFIX')
    )
    name = models.CharField(
        max_length=127, verbose_name=_('MODEL_CURRENCY::NAME')
    )
    nominal = models.PositiveIntegerField(
        default=1, verbose_name=_('MODEL_CURRENCY::NOMINAL')
    )
    rate = models.DecimalField(
        decimal_places=4, max_digits=10, verbose_name=_('MODEL_CURRENCY::RATE')
    )
    updated_at = models.DateTimeField(auto_now=True)

    oracle_currency = ForeignKey(
        'OracleCurrency',
        related_name='procu_currency',
        null=True,
        on_delete=models.CASCADE,
        verbose_name=_('MODEL_CURRENCY::ORACLE_CURRENCY'),
    )

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)


@reversion_register()
class EnquiryCategory(PermissionMixin, SoftDelete, Model):

    name = models.CharField(
        max_length=255, verbose_name=_('MODEL_ENQUIRY_TYPE::NAME')
    )
    managers = models.ManyToManyField(
        User,
        blank=True,
        related_name='types',
        verbose_name=_('MODEL_ENQUIRY_TYPE::MANAGERS'),
    )
    sourcing_time = models.DurationField(
        default=timedelta, verbose_name=_('MODEL_ENQUIRY_TYPE::SOURCING_TIME')
    )
    completion_time = models.DurationField(
        default=timedelta, verbose_name=_('MODEL_ENQUIRY_TYPE::COMPLETION_TIME')
    )


@reversion_register()
class LegalEntity(PermissionMixin, SoftDelete, Model):

    title = models.CharField(
        verbose_name=_('MODEL_LEGAL_ENTITY::TITLE'), max_length=255
    )
    details = models.TextField(
        verbose_name=_('MODEL_LEGAL_ENTITY::DETAILS'), blank=True
    )
    tracker_field = models.CharField(
        max_length=31,
        verbose_name=_('MODEL_LEGAL_ENTITY::TRACKER_FIELD'),
        blank=True,
    )
    oracle_legal_entity = ForeignKey(
        'OracleCompany',
        related_name='legal_entity',
        null=True,
        on_delete=models.CASCADE,
        verbose_name=_('MODEL_LEGAL_ENTITY::ORACLE_LEGAL_ENTITY'),
    )
    can_pay_by_card = models.BooleanField(
        default=False, verbose_name=_('MODEL_LEGAL_ENTITY::CAN_PAY_BY_CARD')
    )
    tracker_queue = models.CharField(
        verbose_name=_('MODEL_LEGAL_ENTITY::TRACKER_QUEUE'),
        max_length=31,
        blank=True,
    )

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)


# ------------------------------------------------------------------------------


@reversion_register(exclude=('updated_at',))
class Quote(BumpUpdated, SoftDelete, PermissionMixin, Model):

    request = ForeignKey(
        'Request', on_delete=models.SET_NULL, related_name='quotes', null=True
    )
    supplier = ForeignKey(
        'Supplier', on_delete=models.PROTECT, related_name='quotes'
    )
    payment_terms = models.TextField(
        blank=True, verbose_name=_('MODEL_QUOTE::TERMS')
    )
    has_offer = models.BooleanField(
        default=False, verbose_name=_('MODEL_QUOTE::HAS_OFFER')
    )
    has_won = models.BooleanField(
        default=False, verbose_name=_('MODEL_QUOTE::HAS_WON')
    )
    created_at = models.DateTimeField(
        default=timezone.now, verbose_name=_('MODEL_QUOTE::CREATED_AT')
    )
    updated_at = models.DateTimeField(
        default=timezone.now, verbose_name=_('MODEL_QUOTE::UPDATED_AT')
    )
    deadline_at = models.DateTimeField(
        null=True, verbose_name=_('MODEL_QUOTE::DEADLINE_AT')
    )
    delivery_at = models.DateField(
        null=True, verbose_name=_('MODEL_QUOTE::DELIVERY_AT')
    )
    delivery_orders = pg_fields.ArrayField(
        models.CharField(blank=True, max_length=31),
        verbose_name=_('MODEL_QUOTE::DELIVERY_ORDERS'),
        default=list,
    )
    status = models.IntegerField(
        choices=enums.QS.keys.items(),
        default=enums.QS.DRAFT,
        verbose_name=_('MODEL_QUOTE::STATUS'),
    )
    reason = models.IntegerField(
        choices=enums.QR.keys.items(),
        default=enums.QR.NONE,
        verbose_name=_('MODEL_QUOTE::REASON'),
    )

    terms = pg_fields.JSONField(
        default=dict, verbose_name=_('MODEL_QUOTE::TERMS')
    )

    # --------------------------------------------------------

    perm_models = ('quote', 'quoteproduct', 'quotecomment', 'invoice')

    with_deleted = models.Manager()
    objects = managers.QuoteManager()

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)
        base_manager_name = 'objects'
        default_manager_name = 'with_deleted'

    def available_statuses(self, user):
        if not user.has_perm('api.progress_enquiry'):
            return []

        available = []

        status, deadline_at = self.status, self.deadline_at
        current = timezone.now()

        non_draft = status != enums.QS.DRAFT

        if non_draft:
            if deadline_at > current:
                available.append(enums.QS.BIDDING)
            else:
                available.append(enums.QS.REVIEW)

            available.append(enums.QS.CHECKOUT)

        if status == enums.QS.CHECKOUT:
            available.append(enums.QS.SHIPPED)

        if non_draft:
            available.append(enums.QS.CLOSED)

        return [s for s in available if s != self.status]

    def available_reasons(self, user):
        non_draft = self.status != enums.QS.DRAFT

        if user.has_perm('api.progress_enquiry') and non_draft:
            return [
                enums.QR.DELIVERED,
                enums.QR.DECLINED,
                enums.QR.IGNORED,
                enums.QR.CANCELLED,
            ]

        return []

    def can_update_status(self, user, status, reason):

        if status not in self.available_statuses(user):
            return False

        if status == enums.QS.CLOSED and reason not in self.available_reasons(
            user
        ):
            return False

        return True

    def permissions(self, user):
        perms = super().permissions(user)

        is_owned = user.is_staff or (self.supplier == user.supplier)
        is_draft = self.status == enums.QS.DRAFT
        is_expired = not user.is_staff and (
            self.status >= enums.QS.CHECKOUT
            or self.deadline_at < timezone.now()
        )

        try:
            if not user.has_perm('api.progress_enquiry'):

                perms -= {
                    'api.create_invoice',
                    'api.update_invoice',
                    'api.delete_invoice',
                }

                access = EnquiryAccess.objects.get(
                    user=user, enquiry__request=self.request_id
                )

                if not (
                    enums.ACCESS_SOURCE.ACCESS in access.sources
                    and access.allow_quote_comments
                ):

                    perms -= {
                        'api.list_invoice',
                        'api.retrieve_invoice',
                        'api.list_quotecomment',
                        'api.create_quotecomment',
                        'api.retrieve_quotecomment',
                        'api.update_quotecomment',
                        'api.delete_quotecomment',
                    }

        except ObjectDoesNotExist:
            pass

        if not is_owned or is_draft or is_expired:

            if not user.is_staff:
                perms.discard('api.update_quote')

            perms.discard('api.create_quoteproduct')
            perms.discard('api.update_quoteproduct')
            perms.discard('api.delete_quoteproduct')

        if not is_owned or is_draft:
            perms.discard('api.create_quotecomment')
            perms.discard('api.update_quotecomment')
            perms.discard('api.delete_quotecomment')

        return perms


@reversion_register(exclude=('updated_at',))
class Enquiry(PermissionMixin, BumpUpdated, Model):

    key = models.CharField(
        verbose_name=_('MODEL_ENQUIRY::KEY'), max_length=31, null=True
    )

    subject = models.CharField(
        verbose_name=_('MODEL_ENQUIRY::SUBJECT'), max_length=255
    )
    created_at = models.DateTimeField(
        verbose_name=_('MODEL_ENQUIRY::CREATED_AT'),
        default=timezone.now,
        editable=False,
    )
    updated_at = models.DateTimeField(
        verbose_name=_('MODEL_ENQUIRY::UPDATED_AT'),
        default=timezone.now,
        editable=False,
    )

    priority = models.IntegerField(
        choices=enums.PRIORITY.keys.items(),
        default=enums.PRIORITY.NORMAL,
        verbose_name=_('MODEL_ENQUIRY::PRIORITY'),
    )
    author = ForeignKey(
        User,
        verbose_name=_('MODEL_ENQUIRY::AUTHOR'),
        related_name='+',
        on_delete=models.CASCADE,
        null=True,
    )
    manager = ForeignKey(
        User,
        verbose_name=_('MODEL_ENQUIRY::MANAGER'),
        related_name='+',
        on_delete=models.CASCADE,
        null=True,
    )
    address = ForeignKey(
        'Address',
        verbose_name=_('MODEL_ENQUIRY::ADDRESS'),
        on_delete=models.CASCADE,
        related_name='enquiries',
        null=True,
    )
    legal_entity = ForeignKey(
        'LegalEntity',
        verbose_name=_('MODEL_ENQUIRY::LEGAL_ENTITY'),
        related_name='+',
        on_delete=models.CASCADE,
        null=True,
    )
    category = ForeignKey(
        'EnquiryCategory',
        verbose_name=_('MODEL_ENQUIRY::CATEGORY'),
        related_name='+',
        on_delete=models.CASCADE,
        null=True,
    )

    cfo = models.ForeignKey(
        'OracleCFO',
        verbose_name=_('MODEL_ENQUIRY::CFO'),
        null=True,
        on_delete=models.CASCADE,
    )
    program = models.ForeignKey(
        'OracleProgram',
        verbose_name=_('MODEL_ENQUIRY::PROGRAM'),
        null=True,
        on_delete=models.CASCADE,
    )
    project = models.ForeignKey(
        'OracleProject',
        verbose_name=_('MODEL_ENQUIRY::PROJECT'),
        null=True,
        on_delete=models.CASCADE,
    )
    task = models.ForeignKey(
        'OracleTask',
        verbose_name=_('MODEL_ENQUIRY::TASK'),
        null=True,
        on_delete=models.CASCADE,
    )
    mvp = models.ForeignKey(
        'OracleMVP',
        verbose_name=_('MODEL_ENQUIRY::MVP'),
        null=True,
        on_delete=models.CASCADE,
    )
    service = models.ForeignKey(
        'OracleService',
        verbose_name=_('MODEL_ENQUIRY::SERVICE'),
        null=True,
        on_delete=models.CASCADE,
    )

    budget_line = models.ForeignKey(
        'OracleBudgetLine',
        verbose_name=_('MODEL_ENQUIRY::BUDGET_LINE'),
        null=True,
        on_delete=models.CASCADE,
    )

    system = models.ForeignKey(
        'OracleSystem',
        verbose_name=_('MODEL_ENQUIRY::SYSTEM'),
        null=True,
        on_delete=models.CASCADE,
    )

    subsystem = models.ForeignKey(
        'OracleSubSystem',
        verbose_name=_('MODEL_ENQUIRY::SUBSYSTEM'),
        null=True,
        on_delete=models.CASCADE,
    )

    no_replacement = models.BooleanField(
        verbose_name=_('MODEL_ENQUIRY::NO_REPLACEMENT'), default=False
    )
    summary = models.TextField(
        verbose_name=_('MODEL_ENQUIRY::SUMMARY'), blank=True
    )
    status = models.IntegerField(
        choices=enums.ES.keys.items(),
        verbose_name=_('MODEL_ENQUIRY::STATUS'),
        default=enums.ES.DRAFT,
    )
    reason = models.IntegerField(
        choices=enums.ER.keys.items(),
        verbose_name=_('MODEL_ENQUIRY::REASON'),
        default=enums.ER.NONE,
    )

    internal_comment = models.TextField(
        verbose_name=_('MODEL_ENQUIRY::INTERNAL_COMMENT'), blank=True
    )

    due_at = models.DateField(
        null=True, verbose_name=_('MODEL_ENQUIRY::DUE_AT')
    )

    initial_products = pg_fields.JSONField(
        default=list, verbose_name=_('MODEL_ENQUIRY::INITIAL_PRODUCTS')
    )

    extra = pg_fields.JSONField(
        default=dict, verbose_name=_('MODEL_ENQUIRY::EXTRA')
    )

    state = models.IntegerField(
        choices=enums.STATE.keys.items(),
        verbose_name=_('MODEL_ENQUIRY::STATE'),
        default=enums.STATE.ACTIVE,
    )

    filter_idx = pg_fields.JSONField(default=dict)

    draft = ForeignKey(
        'FormData',
        verbose_name=_('MODEL_ENQUIRY::DRAFT'),
        related_name='+',
        on_delete=models.SET_NULL,
        null=True,
    )

    # --------------------------------------------------

    objects = managers.EnquiryManager()

    perm_models = (
        'enquirycomment',
        'enquiry',
        'enquiryproduct',
        'quote',
        'link',
    )

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)
        default_permissions = DEFAULT_PERMISSIONS + (
            'checkout',
            'progress',
            'summary',
            'summary_final',
            'all',
        )
        indexes = [models.Index(fields=['key'])]

    def permissions(self, user):
        perms = super().permissions(user)

        if not user.is_staff or self.status == enums.ES.CLOSED:
            perms.discard('api.create_quote')

        if not user.is_staff:
            perms.discard('api.update_enquiry')

        if not user.is_staff or self.status != enums.ES.DRAFT:
            perms.discard('api.create_enquiryproduct')

        if not (
            user.is_staff
            and (enums.QS.BIDDING <= self.status < enums.QS.CHECKOUT)
        ):
            perms.discard('api.checkout_enquiry')

        # ----------------

        perms.discard('api.summary_enquiry')

        can_do_progress = user.has_perm('api.progress_enquiry')
        can_view_all = user.has_perm('api.all_enquiry')

        if can_view_all or can_do_progress:
            perms.add('api.summary_enquiry')

        else:
            only_final_summary = 'api.summary_final_enquiry'

            if self.status >= enums.ES.CHECKOUT and (
                only_final_summary in perms
            ):
                perms.add('api.summary_enquiry')

            else:
                access = self.access.get(user=user)

                if (
                    enums.ACCESS_SOURCE.ACCESS in access.sources
                    and access.allow_quote_comments
                ):
                    perms.add('api.summary_enquiry')

        return perms


class Request(PermissionMixin, Model):

    enquiry = OneToOneField(
        Enquiry, on_delete=models.CASCADE, related_name='request'
    )

    key = models.CharField(
        verbose_name=_('MODEL_REQUEST::KEY'), max_length=31, null=True
    )

    subject = models.TextField(verbose_name=_('MODEL_REQUEST::SUBJECT'))

    address = ForeignKey(
        'Address',
        verbose_name=_('MODEL_REQUEST::ADDRESS'),
        on_delete=models.CASCADE,
        null=True,
    )
    legal_entity = ForeignKey(
        'LegalEntity',
        verbose_name=_('MODEL_REQUEST::LEGAL_ENTITY'),
        related_name='+',
        on_delete=models.CASCADE,
        null=True,
    )

    no_replacement = models.BooleanField(
        verbose_name=_('MODEL_REQUEST::NO_REPLACEMENT'), default=False
    )

    deadline_at = models.DateTimeField(
        verbose_name=_('MODEL_REQUEST::DEADLINE_AT'), null=True
    )

    description = models.TextField(
        verbose_name=_('MODEL_REQUEST::DESCRIPTION'), blank=True
    )

    status = models.IntegerField(
        choices=enums.ES.keys.items(),
        verbose_name=_('MODEL_REQUEST::STATUS'),
        default=enums.ES.DRAFT,
    )
    reason = models.IntegerField(
        choices=enums.ER.keys.items(),
        verbose_name=_('MODEL_REQUEST::REASON'),
        default=enums.ER.NONE,
    )

    summary = models.TextField(
        verbose_name=_('MODEL_REQUEST::SUMMARY'), blank=True
    )

    created_at = models.DateTimeField(
        verbose_name=_('MODEL_REQUEST::CREATED_AT'),
        default=timezone.now,
        editable=False,
    )
    updated_at = models.DateTimeField(
        verbose_name=_('MODEL_REQUEST::UPDATED_AT'),
        default=timezone.now,
        editable=False,
    )

    show_assignee = models.BooleanField(
        verbose_name=_('MODEL_REQUEST::SHOW_ASSIGNEE'), default=False
    )

    delivery_at = models.DateField(
        null=True, verbose_name=_('MODEL_REQUEST::DELIVERY_AT')
    )

    objects = managers.RequestManager()


class EnquirySaving(PermissionMixin, Model):
    enquiry = OneToOneField(
        Enquiry, on_delete=models.CASCADE, related_name='saving', unique=True
    )
    saving = models.DecimalField(decimal_places=4, max_digits=14, null=True)
    best_price = models.DecimalField(decimal_places=4, max_digits=14, null=True)
    expected_price = models.DecimalField(
        decimal_places=4, max_digits=14, null=True
    )
    currency = ForeignKey(
        Currency, on_delete=models.CASCADE, related_name='+', null=True
    )
    note = models.IntegerField(
        choices=enums.SavingNote.keys.items(), default=enums.SavingNote.NONE
    )
    updated_at = models.DateTimeField(default=timezone.now, editable=False)


class EnquiryNote(PermissionMixin, Model):
    enquiry = ForeignKey(
        Enquiry, on_delete=models.CASCADE, related_name='notes'
    )
    user = ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='enquiry_notes',
        verbose_name=_('MODEL_ENQUIRYNOTE::USER'),
    )
    text = models.TextField(verbose_name=_('MODEL_ENQUIRYNOTE::TEXT'))
    created_at = models.DateTimeField(
        verbose_name=_('MODEL_ENQUIRYNOTE::CREATED_AT'),
        default=timezone.now,
        editable=False,
    )
    updated_at = models.DateTimeField(
        verbose_name=_('MODEL_ENQUIRYNOTE::UPDATED_AT'),
        default=timezone.now,
        editable=False,
    )

    objects = managers.EnquiryNoteManager()

    class Meta(PermissionMixin.Meta):
        unique_together = ('enquiry', 'user')


class EnquiryAccess(PermissionMixin, Model):
    enquiry = ForeignKey(
        Enquiry, on_delete=models.CASCADE, related_name='access'
    )
    user = ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='+',
        verbose_name=_('MODEL_ENQUIRYACCESS::USER'),
    )
    sources = pg_fields.ArrayField(
        models.IntegerField(choices=enums.ACCESS_SOURCE.keys.items()),
        verbose_name=_('MODEL_ENQUIRYACCESS::SOURCES'),
        default=list,
    )
    is_subscribed = models.BooleanField(
        default=False, verbose_name=_('MODEL_ENQUIRYACCESS::IS_SUBSCRIBED')
    )
    allow_quote_comments = models.BooleanField(
        default=False,
        verbose_name=_('MODEL_ENQUIRYACCESS::ALLOW_QUOTE_COMMENTS'),
    )
    updated_at = models.DateTimeField(auto_now=True)

    class Meta(PermissionMixin.Meta):
        unique_together = ('enquiry', 'user')


@reversion_register()
class EnquiryComment(PermissionMixin, SoftDelete, Model):

    enquiry = ForeignKey(
        Enquiry, on_delete=models.CASCADE, related_name='comments'
    )
    created_at = models.DateTimeField(default=timezone.now, editable=False)
    updated_at = models.DateTimeField(default=timezone.now, editable=False)
    author = ForeignKey(User, on_delete=models.CASCADE, related_name='+')
    message = FormattedField(
        field_class=models.TextField,
        wf_config='intranet',
        blank=True,
        verbose_name=_('MODEL_ENQUIRY_COMMENT::MESSAGE'),
    )
    invitees = models.ManyToManyField(
        User,
        blank=True,
        related_name='+',
        verbose_name=_('MODEL_ENQUIRY_COMMENT::INVITEES'),
    )
    is_from_email = models.BooleanField(default=False, editable=False)

    # ----------------------------------------------------------

    objects = managers.EnquiryCommentManager()
    with_deleted = models.Manager()

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)
        base_manager_name = 'objects'
        default_manager_name = 'with_deleted'

    def permissions(self, user):
        perms = super().permissions(user)
        perms &= self.enquiry.permissions(user)

        if not (user == self.author):
            perms.discard('api.update_enquirycomment')
            perms.discard('api.delete_enquirycomment')

        return perms


@reversion_register()
class QuoteComment(PermissionMixin, SoftDelete, Model):

    quote = ForeignKey(Quote, on_delete=models.CASCADE, related_name='comments')
    created_at = models.DateTimeField(default=timezone.now, editable=False)
    updated_at = models.DateTimeField(default=timezone.now, editable=False)
    author = ForeignKey(User, on_delete=models.CASCADE, related_name='+')
    message = models.TextField(blank=True)
    message_html = models.TextField(
        editable=False, blank=True, null=True, default=None
    )
    is_from_email = models.BooleanField(default=False, editable=False)
    is_suspicious = models.BooleanField(default=False, editable=False)

    # ----------------------------------------------------------

    objects = managers.QuoteCommentManager()
    with_deleted = models.Manager()

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)
        base_manager_name = 'objects'
        default_manager_name = 'with_deleted'

    def permissions(self, user):
        perms = super().permissions(user)
        perms &= self.quote.permissions(user)

        if not (user == self.author):
            perms.discard('api.update_quotecomment')
            perms.discard('api.delete_quotecomment')

        return perms


@reversion_register()
class EnquiryProduct(SoftDelete, PermissionMixin, Model):

    request = ForeignKey(
        Request, on_delete=models.SET_NULL, related_name='products', null=True
    )
    name = models.CharField(
        max_length=255, verbose_name=_('MODEL_ENQUIRY_PRODUCT::NAME')
    )
    qty = models.PositiveIntegerField(
        verbose_name=_('MODEL_ENQUIRY_PRODUCT::QTY')
    )
    comment = models.TextField(
        blank=True, verbose_name=_('MODEL_ENQUIRY_PRODUCT::COMMENT')
    )

    # ---------------------------------------------------

    objects = ExistingOnlyManager()
    with_deleted = models.Manager()

    class Meta(SoftDelete.Meta, PermissionMixin.Meta):
        ordering = ('pk',)
        base_manager_name = 'objects'
        default_manager_name = 'with_deleted'

    def delete(self, **kwargs):
        for qp in QuoteProduct.objects.filter(enquiry_product=self):
            qp.delete()
        super().delete()


@reversion_register()
class QuoteProduct(PermissionMixin, SoftDelete, Model):

    total = None
    value = None
    best = False
    approx = False

    # -----------------------------

    quote = ForeignKey(
        Quote, related_name='products', on_delete=models.CASCADE, editable=False
    )
    enquiry_product = ForeignKey(
        EnquiryProduct, on_delete=models.CASCADE, related_name='quote_products'
    )
    currency = ForeignKey(Currency, on_delete=models.CASCADE, related_name='+')
    tax = models.DecimalField(decimal_places=2, max_digits=12)
    is_tax_included = models.BooleanField(default=True)
    name = models.CharField(max_length=255, blank=True)
    qty = models.PositiveIntegerField()
    price = models.DecimalField(decimal_places=2, max_digits=12, null=True)
    comment = models.TextField(blank=True)
    delivery_time = models.CharField(max_length=255, blank=True)
    is_replacement = models.BooleanField(default=False)
    is_per_unit = models.BooleanField(default=True)
    snapshot = ForeignKey(
        'QuoteProductSnapshot',
        on_delete=models.CASCADE,
        related_name='+',
        null=True,
    )
    parent = ForeignKey(
        'QuoteProduct', on_delete=models.CASCADE, related_name='+', null=True
    )

    # -----------------------------

    objects = managers.QuoteProductManager()
    for_summary_only = managers.QuoteProductSummaryManager()
    with_deleted = models.Manager()

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)
        base_manager_name = 'objects'
        default_manager_name = 'with_deleted'

    @property
    def formatted_value(self):
        if self.value is None:
            return self.value

        return utils.format_price(self.value)

    @property
    def formatted_price(self):
        if self.price is None:
            return self.price

        return utils.format_price(self.price)

    def prepare(
        self, tax=True, currency=None, totals=True, rounded=False, **kwargs
    ):

        if self.price is None:
            return

        # Initial output value
        value = self.price

        # Four possibilities:
        # tax already included  |  show     including tax  -> do nothing     (0)
        # tax already included  |  show not including tax  -> substract tax (-1)
        # tax is not  included  |  show     including tax  -> add tax       (+1)
        # tax is not  included  |  show not including tax  -> do nothing     (0)
        tax_sign = (not self.is_tax_included) - (not tax)

        if tax_sign < 0:
            value /= 1 + self.tax / 100

        elif tax_sign > 0:
            value *= 1 + self.tax / 100

        if currency:
            rate_from = self.currency.rate / self.currency.nominal
            rate_to = currency.rate / currency.nominal

            value = (value / rate_to) * rate_from

        value = utils.money(value, 2)

        if self.is_per_unit:
            total = value * self.qty
        else:
            total = value
            value = utils.money(value / self.qty, 2)

            if not totals:
                # Indicates that the price is a result of division
                # and may not be accurate.
                self.approx = True

        if totals:
            value = total

        if rounded:
            value = utils.decimal_round(value).to_integral_value()

        self.value = value
        self.total = total

    def permissions(self, user):
        perms = super().permissions(user)
        perms &= self.quote.permissions(user)
        return perms


class QuoteProductSnapshot(PermissionMixin, Model):
    enquiry = ForeignKey(
        Enquiry,
        related_name='snapshots',
        on_delete=models.CASCADE,
        editable=False,
    )
    created_at = models.DateTimeField(default=timezone.now)


@reversion_register()
class Attachment(PermissionMixin, Model):

    author = ForeignKey(
        User, related_name='+', on_delete=models.CASCADE, null=True
    )
    comment = ForeignKey(
        QuoteComment,
        related_name='attachments',
        on_delete=models.CASCADE,
        null=True,
    )
    enquiry = ForeignKey(
        Enquiry, related_name='attachments', on_delete=models.CASCADE, null=True
    )
    request = ForeignKey(
        Request,
        related_name='attachments',
        on_delete=models.SET_NULL,
        null=True,
    )
    enquiry_comment = ForeignKey(
        EnquiryComment,
        related_name='attachments',
        on_delete=models.CASCADE,
        null=True,
    )
    created_at = models.DateTimeField(auto_now_add=True)
    filename = models.CharField(max_length=255, blank=True)
    file = models.FileField(upload_to=get_filename)

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)


class Log(PermissionMixin, Model):

    enquiry = ForeignKey(
        Enquiry, related_name='+', on_delete=models.CASCADE, null=True
    )
    quote = ForeignKey(
        Quote, related_name='+', on_delete=models.CASCADE, null=True
    )
    enquiry_product = ForeignKey(
        EnquiryProduct, related_name='+', on_delete=models.CASCADE, null=True
    )
    user = ForeignKey(User, on_delete=models.CASCADE, related_name='+')
    happened_at = models.DateTimeField(auto_now_add=True)
    type = models.CharField(max_length=255, blank=True)
    data = models.TextField(blank=True, default='{}')
    old = models.TextField(blank=True, default='{}')
    new = models.TextField(blank=True, default='{}')

    # -----------------------------------------------------

    objects = managers.LogManager()

    class Meta(PermissionMixin.Meta):
        ordering = ('-pk',)


class RevisionMeta(Model):

    revision = OneToOneField(
        Revision, on_delete=models.CASCADE, related_name='meta', null=True
    )
    method = models.CharField(max_length=15)
    path = models.CharField(max_length=255)
    version = models.CharField(max_length=31)
    is_processed = models.BooleanField(default=False)

    def mark_processed(self):
        self.is_processed = True
        self.save(update_fields=('is_processed',))


class FormData(Model):
    data = pg_fields.JSONField()
    key = models.TextField(unique=True, default=gen_uuid)
    created_at = models.DateTimeField(default=timezone.now)
    author = ForeignKey(User, related_name='+', on_delete=models.CASCADE)


class QuoteReply(Model):
    token = models.TextField(unique=True, default=gen_uuid)
    quote = OneToOneField(Quote, related_name='+', on_delete=models.CASCADE)


class QuoteRef(Model):
    ref = models.TextField(unique=True, default=gen_uuid)
    quote = ForeignKey(Quote, on_delete=models.CASCADE, related_name='refs')
    user = ForeignKey(User, on_delete=models.CASCADE)
    counter = models.IntegerField()
    created_at = models.DateTimeField(auto_now=True)


class AuthToken(Model):
    token = models.TextField(unique=True, default=gen_uuid)
    user = ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now=True)


class SCSTicket(Model):

    ticket = models.CharField(max_length=127)
    quote = ForeignKey(Quote, on_delete=models.CASCADE)


class Link(PermissionMixin, Model):
    author = ForeignKey(User, related_name='+', on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    enquiry = models.ForeignKey(
        'Enquiry', related_name='links', on_delete=models.CASCADE
    )
    object = models.ForeignKey(
        'LinkObject', null=True, on_delete=models.CASCADE
    )

    key = models.CharField(max_length=63, null=True)
    type = models.IntegerField(choices=enums.LINK.keys.items(), null=True)

    objects = models.Manager()

    def permissions(self, user):
        perms = super().permissions(user)

        if 'api.update_enquiry' not in self.enquiry.permissions(user):
            perms.discard('api.update_link')
            perms.discard('api.delete_link')

        return perms

    class Meta(PermissionMixin.Meta):
        indexes = [models.Index(fields=['key'])]
        unique_together = ('enquiry', 'key')


class LinkObject(Model):
    type = models.IntegerField(choices=enums.LINK.keys.items())
    object_id = models.CharField(max_length=63)
    data = pg_fields.JSONField()
    updated_at = models.DateTimeField(default=timezone.now)
    fetch_attempt = models.IntegerField(default=0)
    fetch_at = models.DateTimeField(null=True)

    objects = models.Manager()

    class Meta:
        indexes = [models.Index(fields=['object_id'])]


# ------------------------------------------------------------------------------


class OracleCFO(SoftDelete, Model):
    key = models.CharField(
        unique=True, max_length=31, verbose_name=_('MODEL_ORACLECFO::KEY')
    )
    name = models.CharField(
        max_length=255, verbose_name=_('MODEL_ORACLECFO::NAME')
    )
    users = models.ManyToManyField(
        User,
        blank=True,
        related_name='cfos',
        verbose_name=_('MODEL_ORACLECFO::USERS'),
    )


class OracleProgram(SoftDelete, Model):
    key = models.CharField(
        max_length=31, unique=True, verbose_name=_('MODEL_ORACLEPROGRAM::KEY')
    )
    name = models.CharField(
        max_length=255, verbose_name=_('MODEL_ORACLEPROGRAM::NAME')
    )


class OracleProject(SoftDelete, Model):
    key = models.CharField(
        max_length=31, unique=True, verbose_name=_('MODEL_ORACLEPROJECT::KEY')
    )
    name = models.CharField(
        max_length=255, verbose_name=_('MODEL_ORACLEPROJECT::NAME')
    )
    program = ForeignKey(
        OracleProgram,
        null=True,
        related_name='projects',
        verbose_name=_('MODEL_ORACLEPROJECT::PROGRAM'),
        on_delete=models.CASCADE,
    )
    company = ForeignKey(
        'OracleCompany',
        null=True,
        related_name='projects',
        verbose_name=_('MODEL_ORACLEPROJECT::COMPANY'),
        on_delete=models.CASCADE,
    )


class OracleTask(SoftDelete, Model):
    key = models.CharField(
        max_length=31, unique=True, verbose_name=_('MODEL_ORACLETASK::KEY')
    )
    name = models.CharField(
        max_length=255, verbose_name=_('MODEL_ORACLETASK::NAME')
    )
    project = ForeignKey(
        OracleProject,
        null=True,
        related_name='tasks',
        verbose_name=_('MODEL_ORACLETASK::PROJECT'),
        on_delete=models.CASCADE,
    )


class OracleMVP(SoftDelete, Model):
    key = models.CharField(
        max_length=31, unique=True, verbose_name=_('MODEL_ORACLEMVP::KEY')
    )
    name = models.CharField(
        max_length=255, verbose_name=_('MODEL_ORACLEMVP::NAME')
    )


class OracleProductLine(SoftDelete, Model):
    key = models.CharField(
        max_length=31,
        unique=True,
        verbose_name=_('MODEL_ORACLEPRODUCTLINE::KEY'),
    )
    name = models.CharField(
        max_length=255, verbose_name=_('MODEL_ORACLEPRODUCTLINE::NAME')
    )


class OraclePurchaseGroup(SoftDelete, Model):
    key = models.IntegerField(
        unique=True, verbose_name=_('MODEL_ORACLEPURCHASEGROUP::KEY')
    )
    name = models.CharField(
        max_length=255, verbose_name=_('MODEL_ORACLEPURCHASEGROUP::NAME')
    )


class OracleService(SoftDelete, Model):
    key = models.TextField(
        unique=True, blank=True, verbose_name=_('MODEL_ORACLESERVICE::KEY')
    )
    name = models.CharField(
        max_length=255, verbose_name=_('MODEL_ORACLESERVICE::NAME')
    )


# ------------------------------------------------------------------------------


class OracleCurrency(SoftDelete, Model):
    key = models.CharField(
        max_length=15, unique=True, verbose_name=_('MODEL_ORACLECURRENCY::KEY')
    )
    name = models.CharField(
        max_length=255, verbose_name=_('MODEL_ORACLECURRENCY::NAME')
    )


class OracleCompany(SoftDelete, Model):
    orgid = models.IntegerField(
        unique=True, verbose_name=_('MODEL_ORACLECOMPANY::ORGID')
    )
    inn = models.CharField(
        max_length=15, null=True, verbose_name=_('MODEL_ORACLECOMPANY::INN')
    )
    name = models.CharField(
        max_length=255, verbose_name=_('MODEL_ORACLECOMPANY::NAME')
    )
    shortname = models.CharField(
        max_length=15, verbose_name=_('MODEL_ORACLECOMPANY::SHORTNAME')
    )


class OracleSupplier(SoftDelete, Model):
    key = models.TextField(
        unique=True, verbose_name=_('MODEL_ORACLESUPPLIER::KEY')
    )
    name = models.TextField(verbose_name=_('MODEL_ORACLESUPPLIER::NAME'))
    full_name = models.TextField(
        verbose_name=_('MODEL_ORACLESUPPLIER::FULL_NAME')
    )
    inn = models.TextField(
        null=True, verbose_name=_('MODEL_ORACLESUPPLIER::INN')
    )


class OracleImportLog(Model):
    key = models.TextField(primary_key=True)
    last_id = models.IntegerField(default=0)
    last_updated_at = models.DateTimeField()


class Invoice(SoftDelete, PermissionMixin, Model):

    enquiry = ForeignKey(
        Enquiry, on_delete=models.PROTECT, related_name='invoices', null=True
    )
    supplier = ForeignKey(
        Supplier, on_delete=models.PROTECT, related_name='invoices', null=True
    )
    created_at = models.DateTimeField(
        auto_now_add=True, verbose_name=_('MODEL_INVOICE::CREATED_AT')
    )
    updated_at = models.DateTimeField(
        auto_now=True, verbose_name=_('MODEL_INVOICE::UPDATED_AT')
    )
    legal_entity = ForeignKey(
        LegalEntity,
        on_delete=models.PROTECT,
        related_name='invoices',
        null=True,
        verbose_name=_('MODEL_INVOICE::LEGAL_ENTITY'),
    )

    # --------------------------------------------------------

    objects = managers.InvoiceManager()
    with_deleted = models.Manager()

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)
        base_manager_name = 'objects'
        default_manager_name = 'with_deleted'

    def permissions(self, user):
        perms = super().permissions(user)

        if not user.is_staff or self.enquiry.status == enums.ES.CLOSED:
            perms -= {'api.update_invoice', 'api.delete_invoice'}

        return perms


class OracleBudgetLine(SoftDelete, Model):
    key = models.TextField(
        unique=True, verbose_name=_('MODEL_ORACLEBUDGETLINE::KEY')
    )
    name = models.TextField(verbose_name=_('MODEL_ORACLEBUDGETLINE::NAME'))


class OracleSystem(SoftDelete, Model):
    key = models.TextField(
        unique=True, verbose_name=_('MODEL_ORACLESYSTEM::KEY')
    )
    name = models.TextField(verbose_name=_('MODEL_ORACLESYSTEM::NAME'))


class OracleSubSystem(SoftDelete, Model):
    key = models.TextField(
        unique=True, verbose_name=_('MODEL_ORACLESUBSYSTEM::KEY')
    )
    name = models.TextField(verbose_name=_('MODEL_ORACLESUBSYSTEM::NAME'))
    system = ForeignKey(
        OracleSystem,
        null=True,
        related_name='subsystems',
        verbose_name=_('MODEL_ORACLESUBSYSTEM::SYSTEM'),
        on_delete=models.CASCADE,
    )


class InvoiceProduct(SoftDelete, PermissionMixin, Model):
    invoice = ForeignKey(
        Invoice,
        related_name='products',
        on_delete=models.CASCADE,
        editable=False,
    )
    name = models.TextField(verbose_name=_('MODEL_INVOICEPRODUCT::NAME'))
    qty = models.DecimalField(
        decimal_places=3,
        max_digits=12,
        verbose_name=_('MODEL_INVOICEPRODUCT::QTY'),
    )
    unit = ForeignKey(
        Unit,
        on_delete=models.CASCADE,
        related_name='+',
        verbose_name=_('MODEL_INVOICEPRODUCT::UNIT'),
    )
    price = models.DecimalField(
        decimal_places=2,
        max_digits=12,
        verbose_name=_('MODEL_INVOICEPRODUCT::PRICE'),
    )
    price_for = models.IntegerField(
        choices=enums.PRICE.keys.items(),
        verbose_name=_('MODEL_INVOICEPRODUCT::PRICE_FOR'),
    )
    currency = ForeignKey(
        Currency,
        on_delete=models.CASCADE,
        related_name='+',
        verbose_name=_('MODEL_INVOICEPRODUCT::CURRENCY'),
    )
    tax = models.DecimalField(
        decimal_places=2,
        max_digits=12,
        verbose_name=_('MODEL_INVOICEPRODUCT::TAX'),
    )
    tax_mode = models.IntegerField(
        choices=enums.TAX.keys.items(),
        verbose_name=_('MODEL_INVOICEPRODUCT::TAX_MODE'),
    )

    delivery_expected_at = models.DateField(
        null=True, verbose_name=_('MODEL_INVOICEPRODUCT::DELIVERY_EXPECTED_AT')
    )
    delivery_happened_at = models.DateField(
        null=True, verbose_name=_('MODEL_INVOICEPRODUCT::DELIVERY_HAPPENED_AT')
    )

    address = ForeignKey(
        Address,
        on_delete=models.PROTECT,
        related_name='invoice_products',
        null=True,
        verbose_name=_('MODEL_INVOICEPRODUCT::ADDRESS'),
    )

    # -----------------------------

    objects = managers.ExistingOnlyManager()
    with_deleted = models.Manager()

    class Meta(PermissionMixin.Meta):
        ordering = ('pk',)
        base_manager_name = 'objects'
        default_manager_name = 'with_deleted'

    def permissions(self, user):
        perms = super().permissions(user)
        enquiry = self.invoice.enquiry

        if not (user.is_staff or enquiry.status < enums.ES.CLOSED):
            perms.discard('api.update_invoiceproduct')

        return perms


# ------------------------------------------------------------------------------


class Dashboard(SoftDelete, PermissionMixin, Model):

    author = ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='dashboards',
        verbose_name=_('DASHBOARD::AUTHOR'),
    )

    name = models.TextField(blank=False, verbose_name=_('DASHBOARD::NAME'))

    is_public = models.BooleanField(
        default=False, verbose_name=_('DASHBOARD::IS_PUBLIC')
    )

    created_at = models.DateTimeField(default=timezone.now)

    # ---------------------------------------------------

    objects = managers.ExistingOnlyManager()
    with_deleted = models.Manager()

    class Meta(PermissionMixin.Meta):
        base_manager_name = 'objects'
        default_manager_name = 'with_deleted'
        ordering = ('pk',)


class DashboardWidget(SoftDelete, PermissionMixin, Model):

    dashboard = ForeignKey(
        Dashboard,
        on_delete=models.CASCADE,
        related_name='widgets',
        verbose_name=_('DASHBOARDWIDGET::DASHBOARD'),
    )

    name = models.TextField(
        blank=False, verbose_name=_('DASHBOARDWIDGET::NAME')
    )

    filter = pg_fields.JSONField(
        default=dict, verbose_name=_('DASHBOARDWIDGET::FILTER')
    )

    limit = models.IntegerField(verbose_name=_('DASHBOARDWIDGET::LIMIT'))

    # ---------------------------------------------------

    objects = managers.ExistingOnlyManager()
    with_deleted = models.Manager()

    class Meta(PermissionMixin.Meta):
        base_manager_name = 'objects'
        default_manager_name = 'with_deleted'
        ordering = ('pk',)


class EnquiryLog(Model):

    enquiry = ForeignKey('Enquiry', on_delete=models.PROTECT)

    status = models.IntegerField()
    reason = models.IntegerField()
    state = models.IntegerField()

    created_at = models.DateTimeField(auto_now_add=True)


class BUYTicket(Model):
    key = models.TextField(unique=True)
    created_at = models.DateTimeField()
