"""
Veventa — events ERP, as a module of The Panel.

Replaces the standalone single-file app. What that held as one encrypted blob
is here as linked rows, so a failed save costs one record rather than a day,
every page has a real URL, and the question "everything we have ever done with
this venue" becomes a query instead of a memory.

Three things carried over from the old engine on purpose:

  * Dates keep a Jalali value beside the Gregorian one. Iran runs on the
    Persian calendar and converting at display time loses the date that was
    actually agreed.
  * Currency sits on the event. One company runs shows in eight countries and
    those totals must not add up together without an FX rate.
  * Every counterparty is scored. The ratings are the accumulated judgement of
    years of trading and are worth more than the contact details.
"""

from django.db import models


class TenantOwned(models.Model):
    """Every record belongs to one tenant and is never visible to another."""

    tenant = models.ForeignKey(
        'tenants.Tenant', on_delete=models.CASCADE,
        null=True, blank=True, related_name='+',
    )

    class Meta:
        abstract = True


class Rated(models.Model):
    """Shared scoring. Each score is 0 to 10, 0 meaning not yet rated.

    Kept as columns rather than a separate table because every one of them is
    read on every list view, and a join per column made the old reports slow.
    """

    r_again   = models.PositiveSmallIntegerField(default=0, verbose_name='Work again')
    r_quality = models.PositiveSmallIntegerField(default=0, verbose_name='Quality')
    r_value   = models.PositiveSmallIntegerField(default=0, verbose_name='Value')
    r_comms   = models.PositiveSmallIntegerField(default=0, verbose_name='Communication')
    r_reliab  = models.PositiveSmallIntegerField(default=0, verbose_name='Reliability')

    class Meta:
        abstract = True

    @property
    def rating_avg(self):
        vals = [v for v in (self.r_again, self.r_quality, self.r_value,
                            self.r_comms, self.r_reliab) if v]
        return round(sum(vals) / len(vals), 1) if vals else None


class Company(TenantOwned):
    """The trading entity. One tenant may run more than one."""

    name       = models.CharField(max_length=160)
    code       = models.CharField(max_length=20, blank=True, default='')
    country    = models.CharField(max_length=80, blank=True, default='')
    currency   = models.CharField(max_length=3, default='GBP')

    # A regional operator works in one country and one currency, and showing
    # it an FX table is noise. A multi-region one cannot add its books
    # together without rates, so there the rates are load bearing.
    is_multi_currency = models.BooleanField(default=False)
    is_multi_region   = models.BooleanField(default=False)

    # Which calendar this company signs contracts against. The other is kept
    # alongside and follows it, rather than both being typed and drifting.
    CAL = [('gregorian', 'Gregorian'), ('jalali', 'Jalali')]
    calendar   = models.CharField(max_length=10, choices=CAL, default='gregorian')

    is_active  = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = 'Companies'
        ordering = ['name']

    def __str__(self):
        return self.name


class Brand(TenantOwned):
    """The name the public sees. HP Events, Dorehami, AVA Events and so on all
    sit under one company and keep their own books."""

    company    = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='brands')
    name       = models.CharField(max_length=160)
    code       = models.CharField(max_length=20, blank=True, default='')
    colour     = models.CharField(max_length=7, blank=True, default='',
                 help_text='Hex, used in charts and badges')
    is_active  = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name


class Venue(TenantOwned, Rated):
    name       = models.CharField(max_length=160)
    city       = models.CharField(max_length=120, blank=True, default='')
    country    = models.CharField(max_length=80, blank=True, default='')
    capacity   = models.PositiveIntegerField(null=True, blank=True)
    manager    = models.CharField(max_length=160, blank=True, default='')
    phone      = models.CharField(max_length=40, blank=True, default='')
    email      = models.EmailField(blank=True, default='')
    address    = models.CharField(max_length=255, blank=True, default='')
    website    = models.URLField(blank=True, default='')
    note       = models.TextField(blank=True, default='')
    is_active  = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name


class Artist(TenantOwned, Rated):
    """The act. Stage name and contract name are both kept because one goes on
    the poster and the other goes in the agreement."""

    FEE_PER_SHOW = 'show'
    FEE_PER_DAY  = 'day'
    FEE_SPLIT    = 'split'
    FEE_CHOICES = [
        (FEE_PER_SHOW, 'Per show'),
        (FEE_PER_DAY,  'Per day'),
        (FEE_SPLIT,    'Door split'),
    ]

    name        = models.CharField(max_length=160, verbose_name='Stage name')
    legal_name  = models.CharField(max_length=160, blank=True, default='')
    is_dj       = models.BooleanField(default=False)
    roles       = models.CharField(max_length=200, blank=True, default='',
                  help_text='Singer, Band, DJ, Host, Comedian')
    experience  = models.CharField(max_length=120, blank=True, default='')
    country     = models.CharField(max_length=80, blank=True, default='')

    fee         = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    fee_basis   = models.CharField(max_length=10, choices=FEE_CHOICES, blank=True, default='')
    fee_currency= models.CharField(max_length=3, blank=True, default='')

    manager     = models.CharField(max_length=160, blank=True, default='')
    manager_tel = models.CharField(max_length=40, blank=True, default='')
    label       = models.CharField(max_length=160, blank=True, default='')
    label_tel   = models.CharField(max_length=40, blank=True, default='')

    instagram   = models.CharField(max_length=120, blank=True, default='')
    youtube     = models.CharField(max_length=120, blank=True, default='')
    spotify     = models.CharField(max_length=200, blank=True, default='')
    website     = models.URLField(blank=True, default='')

    # Artist specific scores, on top of the shared five.
    r_audience  = models.PositiveSmallIntegerField(default=0, verbose_name='Audience draw')
    r_stage     = models.PositiveSmallIntegerField(default=0, verbose_name='Stage presence')
    r_promo     = models.PositiveSmallIntegerField(default=0, verbose_name='Promotes the show')
    r_coop      = models.PositiveSmallIntegerField(default=0, verbose_name='Co-operation')

    note        = models.TextField(blank=True, default='')
    is_active   = models.BooleanField(default=True)
    created_at  = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name


class Promoter(TenantOwned, Rated):
    name       = models.CharField(max_length=160)
    manager    = models.CharField(max_length=160, blank=True, default='')
    phone      = models.CharField(max_length=40, blank=True, default='')
    email      = models.EmailField(blank=True, default='')
    city       = models.CharField(max_length=120, blank=True, default='')
    country    = models.CharField(max_length=80, blank=True, default='')
    categories = models.CharField(max_length=200, blank=True, default='')

    r_financial= models.PositiveSmallIntegerField(default=0, verbose_name='Financial conduct')
    r_fit      = models.PositiveSmallIntegerField(default=0, verbose_name='Audience fit')
    r_promo    = models.PositiveSmallIntegerField(default=0, verbose_name='Promotion')
    r_reach    = models.PositiveSmallIntegerField(default=0, verbose_name='Reach')
    r_sales    = models.PositiveSmallIntegerField(default=0, verbose_name='Ticket sales')

    note       = models.TextField(blank=True, default='')
    is_active  = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name


class Supplier(TenantOwned, Rated):
    """Sound, lighting, staging, security, catering, printing, transport."""

    name       = models.CharField(max_length=160)
    category   = models.CharField(max_length=120, blank=True, default='')
    categories = models.CharField(max_length=200, blank=True, default='')
    contact    = models.CharField(max_length=160, blank=True, default='')
    phone      = models.CharField(max_length=40, blank=True, default='')
    email      = models.EmailField(blank=True, default='')
    city       = models.CharField(max_length=120, blank=True, default='')
    country    = models.CharField(max_length=80, blank=True, default='')
    website    = models.URLField(blank=True, default='')

    r_flex     = models.PositiveSmallIntegerField(default=0, verbose_name='Flexibility')
    r_problem  = models.PositiveSmallIntegerField(default=0, verbose_name='Handles problems')
    r_prof     = models.PositiveSmallIntegerField(default=0, verbose_name='Professionalism')

    note       = models.TextField(blank=True, default='')
    is_active  = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name


class Partner(TenantOwned, Rated):
    """Sponsors, media partners, co-promoters, anyone who brings something
    other than a service."""

    name       = models.CharField(max_length=160)
    manager    = models.CharField(max_length=160, blank=True, default='')
    phone      = models.CharField(max_length=40, blank=True, default='')
    email      = models.EmailField(blank=True, default='')
    city       = models.CharField(max_length=120, blank=True, default='')
    country    = models.CharField(max_length=80, blank=True, default='')
    website    = models.URLField(blank=True, default='')
    categories = models.CharField(max_length=200, blank=True, default='')

    r_flex     = models.PositiveSmallIntegerField(default=0, verbose_name='Flexibility')

    note       = models.TextField(blank=True, default='')
    is_active  = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name


class EventQuerySet(models.QuerySet):
    """Two rules, not one, because a cancelled show is not nothing.

    booked() is for performance. A date that has not happened, or was never
    confirmed, is a plan, and a cancelled one sold nothing, so neither says
    anything about an act, a room or a promoter.

    financial() is for money. A cancellation still spent: the deposit went,
    the artwork was printed, the venue took its fee. That loss is real and has
    to appear in the figures, or the estate looks more profitable than it is.

    An event can be cancelled five minutes before doors. Nothing counts it as
    delivered until it is.
    """

    def booked(self):
        return self.filter(status__in=(Event.STATUS_IN_PROGRESS,
                                       Event.STATUS_DELIVERED))

    def financial(self):
        return self.filter(status__in=(Event.STATUS_IN_PROGRESS,
                                       Event.STATUS_DELIVERED,
                                       Event.STATUS_CANCELLED,
                                       Event.STATUS_POSTPONED))

    def plans(self):
        return self.filter(status=Event.STATUS_DRAFT)


class Event(TenantOwned):
    MODE_CHOICES = [
        ('promoted', 'Promoted'), ('hire', 'Venue Hire'),
        ('partner', 'Partnership'), ('service', 'Service'), ('other', 'Other'),
    ]
    STATUS_DRAFT       = 'draft'
    STATUS_IN_PROGRESS = 'in_progress'
    STATUS_DELIVERED   = 'delivered'
    STATUS_CANCELLED   = 'cancelled'
    STATUS_POSTPONED   = 'postponed'
    STATUS_CHOICES = [
        (STATUS_DRAFT,       'Draft'),
        (STATUS_IN_PROGRESS, 'In Progress'),
        (STATUS_DELIVERED,   'Delivered'),
        (STATUS_CANCELLED,   'Cancelled'),
        (STATUS_POSTPONED,   'Postponed'),
    ]

    objects = EventQuerySet.as_manager()

    name        = models.CharField(max_length=200)
    slug        = models.SlugField(max_length=220, blank=True, default='')
    brand       = models.ForeignKey(Brand, on_delete=models.SET_NULL, null=True, blank=True,
                  related_name='events')
    event_type  = models.CharField(max_length=60, blank=True, default='')
    family      = models.CharField(max_length=60, blank=True, default='',
                  help_text='Groups a recurring series together')
    mode        = models.CharField(max_length=20, choices=MODE_CHOICES, default='promoted')
    status      = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
    categories  = models.CharField(max_length=200, blank=True, default='')

    date        = models.DateField(null=True, blank=True)
    # A postponement is a new date, not a new event. The record keeps its
    # identity, its costs and its history, and only the date moves. It becomes
    # a different event when both the venue and the act have changed, because
    # at that point nothing of the original show is left.
    #
    # The date it was first meant to happen is kept so the money spent on the
    # first attempt stays attached to the show that caused it.
    original_date = models.DateField(null=True, blank=True)
    postponed_reason = models.CharField(max_length=255, blank=True, default='')
    attempt          = models.PositiveSmallIntegerField(default=1,
                       help_text='Raised each time the show is postponed and rebooked')
    date_jalali = models.CharField(max_length=10, blank=True, default='',
                  help_text='Persian calendar date, for example 1405/08/18')
    doors_time  = models.CharField(max_length=10, blank=True, default='')
    year        = models.PositiveSmallIntegerField(null=True, blank=True)

    venue       = models.ForeignKey(Venue, on_delete=models.SET_NULL, null=True, blank=True,
                  related_name='events')
    city        = models.CharField(max_length=120, blank=True, default='')
    country     = models.CharField(max_length=80, blank=True, default='')
    region      = models.CharField(max_length=80, blank=True, default='')

    main_act    = models.ForeignKey(Artist, on_delete=models.SET_NULL, null=True, blank=True,
                  related_name='headlined')
    artists     = models.ManyToManyField(Artist, through='EventArtist', blank=True,
                  related_name='events')
    promoter    = models.ForeignKey(Promoter, on_delete=models.SET_NULL, null=True, blank=True,
                  related_name='events')
    suppliers   = models.ManyToManyField(Supplier, through='EventSupplier', blank=True,
                  related_name='events')
    partners    = models.ManyToManyField(Partner, through='EventPartner', blank=True,
                  related_name='events')

    currency    = models.CharField(max_length=3, default='GBP')
    progress    = models.PositiveSmallIntegerField(default=0, help_text='0 to 100')

    # Who sold the tickets and what they took. The platform is a Partner with
    # a ticketing category, so its record carries the rating and the history
    # rather than the name being retyped on every event.
    ticket_platform    = models.ForeignKey(Partner, on_delete=models.SET_NULL,
                         null=True, blank=True, related_name='ticketed_events')
    box_office_charges = models.DecimalField(max_digits=12, decimal_places=2,
                         default=0, help_text='What the platform kept')
    note        = models.TextField(blank=True, default='')

    created_at  = models.DateTimeField(auto_now_add=True)
    updated_at  = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-date', '-id']
        indexes = [
            models.Index(fields=['tenant', 'date']),
            models.Index(fields=['tenant', 'status']),
        ]

    def __str__(self):
        return self.name


class EventArtist(models.Model):
    """One act on one event, with what it cost and what it was billed as."""

    BILLING = [('headline', 'Headline'), ('support', 'Support'), ('guest', 'Guest')]

    event    = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='artist_links')
    artist   = models.ForeignKey(Artist, on_delete=models.CASCADE, related_name='event_links')
    billing  = models.CharField(max_length=10, choices=BILLING, default='support')
    fee      = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    currency = models.CharField(max_length=3, blank=True, default='')
    is_paid  = models.BooleanField(default=False)
    note     = models.CharField(max_length=255, blank=True, default='')
    order    = models.PositiveSmallIntegerField(default=0)

    class Meta:
        ordering = ['order', 'id']
        unique_together = [('event', 'artist')]


class EventSupplier(models.Model):
    """A supplier engaged on an event, the service bought and what it cost."""

    event    = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='supplier_links')
    supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE, related_name='event_links')
    service  = models.CharField(max_length=160, blank=True, default='')
    amount   = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    currency = models.CharField(max_length=3, blank=True, default='')
    is_paid  = models.BooleanField(default=False)
    note     = models.CharField(max_length=255, blank=True, default='')

    class Meta:
        unique_together = [('event', 'supplier', 'service')]


class EventPartner(models.Model):
    """A partner on an event and what they brought."""

    KIND = [('sponsor', 'Sponsor'), ('media', 'Media'),
            ('co_promoter', 'Co-promoter'), ('other', 'Other')]

    event        = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='partner_links')
    partner      = models.ForeignKey(Partner, on_delete=models.CASCADE, related_name='event_links')
    kind         = models.CharField(max_length=15, choices=KIND, default='sponsor')
    contribution = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    currency     = models.CharField(max_length=3, blank=True, default='')
    in_kind      = models.CharField(max_length=200, blank=True, default='')
    note         = models.CharField(max_length=255, blank=True, default='')

    class Meta:
        unique_together = [('event', 'partner')]


class TicketTier(models.Model):
    """A price band on an event. Zones and holds are kept because the old app
    had them and a hold is not a sale."""

    event      = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='tiers')
    name       = models.CharField(max_length=80)
    zone       = models.CharField(max_length=80, blank=True, default='')
    colour     = models.CharField(max_length=7, blank=True, default='')
    price      = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    allocation = models.PositiveIntegerField(default=0)
    holds      = models.PositiveIntegerField(default=0)
    sold       = models.PositiveIntegerField(default=0)
    order      = models.PositiveSmallIntegerField(default=0)

    class Meta:
        ordering = ['order', 'id']

    @property
    def available(self):
        return max(0, self.allocation - self.holds - self.sold)

    @property
    def gross(self):
        return self.price * self.sold


class BudgetLine(models.Model):
    """Money out, as rows. A total nobody can take apart is a total nobody
    trusts."""

    event    = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='budget')
    category = models.CharField(max_length=80, blank=True, default='')
    item     = models.CharField(max_length=160)
    description = models.CharField(max_length=255, blank=True, default='')
    quantity = models.DecimalField(max_digits=10, decimal_places=2, default=1)
    estimate = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    actual   = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    owner    = models.CharField(max_length=120, blank=True, default='')
    supplier = models.ForeignKey(Supplier, on_delete=models.SET_NULL, null=True, blank=True,
               related_name='budget_lines')
    is_done  = models.BooleanField(default=False)
    is_paid  = models.BooleanField(default=False)
    due_date = models.DateField(null=True, blank=True)
    # A postponed show pays for its marketing twice. Without knowing which
    # attempt a line belongs to, the second round looks like overspending
    # rather than the price of the postponement.
    attempt  = models.PositiveSmallIntegerField(default=1)

    class Meta:
        ordering = ['category', 'id']

    @property
    def total(self):
        return (self.actual if self.actual is not None else self.estimate) * self.quantity


class IncomeLine(models.Model):
    """Money in that is not a ticket: sponsorship, bar, merchandise, grant."""

    event       = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='income')
    kind        = models.CharField(max_length=80, blank=True, default='')
    description = models.CharField(max_length=255, blank=True, default='')
    amount      = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    partner     = models.ForeignKey(Partner, on_delete=models.SET_NULL, null=True, blank=True,
                  related_name='income_lines')
    received    = models.BooleanField(default=False)
    due_date    = models.DateField(null=True, blank=True)

    class Meta:
        ordering = ['kind', 'id']


class PromotionChannel(models.Model):
    """Where the show was advertised and what it cost."""

    event   = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='promotion')
    channel = models.CharField(max_length=120)
    spend   = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    reach   = models.PositiveIntegerField(null=True, blank=True)
    note    = models.CharField(max_length=255, blank=True, default='')
    attempt = models.PositiveSmallIntegerField(default=1)

    class Meta:
        ordering = ['channel']


class SeatingSection(models.Model):
    """Seated shows: a block of the room at one price."""

    event   = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='seating')
    section = models.CharField(max_length=80)
    floor   = models.CharField(max_length=40, blank=True, default='')
    seats   = models.PositiveIntegerField(default=0)
    price   = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    order   = models.PositiveSmallIntegerField(default=0)

    class Meta:
        ordering = ['order', 'section']

    @property
    def total(self):
        return self.seats * self.price


class PlanItem(models.Model):
    """The run of deadlines that gets an event delivered."""

    event    = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='plan')
    task     = models.CharField(max_length=200)
    owner    = models.CharField(max_length=120, blank=True, default='')
    due_date = models.DateField(null=True, blank=True)
    is_done  = models.BooleanField(default=False)
    done_at  = models.DateTimeField(null=True, blank=True)
    order    = models.PositiveSmallIntegerField(default=0)

    class Meta:
        ordering = ['order', 'due_date', 'id']


class Document(TenantOwned):
    """Riders, stage plans, contracts and forms, filed against whichever
    record they belong to."""

    KIND = [('rider', 'Rider'), ('stage_plan', 'Stage Plan'),
            ('contract', 'Contract'), ('invoice', 'Invoice'),
            ('form', 'Form'), ('other', 'Other')]

    title    = models.CharField(max_length=200)
    kind     = models.CharField(max_length=20, choices=KIND, default='other')
    file     = models.FileField(upload_to='veventa/docs/', blank=True, null=True)
    event    = models.ForeignKey(Event, on_delete=models.CASCADE, null=True, blank=True,
               related_name='documents')
    artist   = models.ForeignKey(Artist, on_delete=models.CASCADE, null=True, blank=True,
               related_name='documents')
    venue    = models.ForeignKey(Venue, on_delete=models.CASCADE, null=True, blank=True,
               related_name='documents')
    supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE, null=True, blank=True,
               related_name='documents')
    note     = models.CharField(max_length=255, blank=True, default='')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at']


class EventImage(models.Model):
    event   = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='images')
    image   = models.ImageField(upload_to='veventa/gallery/')
    caption = models.CharField(max_length=200, blank=True, default='')
    order   = models.PositiveSmallIntegerField(default=0)

    class Meta:
        ordering = ['order', 'id']


class EventAssessment(models.Model):
    """The AI read on an event, kept as a record rather than regenerated, so
    what was decided on can be seen later."""

    event           = models.OneToOneField(Event, on_delete=models.CASCADE,
                      related_name='assessment')
    headline        = models.CharField(max_length=255, blank=True, default='')
    demand          = models.TextField(blank=True, default='')
    pricing         = models.TextField(blank=True, default='')
    promotion       = models.TextField(blank=True, default='')
    risks           = models.TextField(blank=True, default='')
    opportunities   = models.TextField(blank=True, default='')
    recommendations = models.TextField(blank=True, default='')
    competing       = models.TextField(blank=True, default='')
    band            = models.CharField(max_length=40, blank=True, default='')
    confidence      = models.PositiveSmallIntegerField(default=0)
    model_used      = models.CharField(max_length=60, blank=True, default='')
    created_at      = models.DateTimeField(auto_now_add=True)


class FxRate(TenantOwned):
    """One rate per currency against the tenant's base. Totals never cross
    currencies without one of these."""

    code       = models.CharField(max_length=3)
    rate       = models.DecimalField(max_digits=14, decimal_places=6, default=1)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['code']
        unique_together = [('tenant', 'code')]

    def __str__(self):
        return f'{self.code} {self.rate}'


class TierColour(TenantOwned):
    """The named colours used for tiers across the interface."""

    name  = models.CharField(max_length=60)
    hex   = models.CharField(max_length=7)
    order = models.PositiveSmallIntegerField(default=0)

    class Meta:
        ordering = ['order', 'name']

    def __str__(self):
        return self.name


class CalendarNote(TenantOwned):
    """A date worth knowing about, added by hand.

    Public holidays are not stored. They are computed per region from their own
    rules, UK ones off Easter and nth weekday, Persian ones off their calendar,
    so any year works without anybody maintaining a list. This table is only
    for what you add: a competing show, a venue closure, a date to avoid.
    """

    KIND = [('avoid', 'Avoid this date'), ('watch', 'Worth watching'),
            ('competing', 'Competing event'), ('closure', 'Venue closure'),
            ('note', 'Note')]

    date        = models.DateField()
    date_jalali = models.CharField(max_length=10, blank=True, default='')
    title       = models.CharField(max_length=200)
    kind        = models.CharField(max_length=12, choices=KIND, default='note')
    region      = models.CharField(max_length=80, blank=True, default='',
                  help_text='Which market this matters in. Blank means everywhere')
    body        = models.TextField(blank=True, default='')
    created_at  = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['date']
        indexes = [models.Index(fields=['tenant', 'date'])]

    def __str__(self):
        return f'{self.date} {self.title}'


class AudienceModel(models.Model):
    """What the room could realistically hold and who could realistically come.

    This is what feeds the Market driver. Capacity on its own says nothing:
    a thousand seats in a city that cannot reach the venue, at a price the
    audience will not pay, on a night three other shows are running, is not a
    thousand tickets. Each slider is a percentage and they multiply down from
    the reachable audience to an estimate worth planning against.
    """

    event             = models.OneToOneField(Event, on_delete=models.CASCADE,
                        related_name='audience')
    seats_per_show    = models.PositiveIntegerField(default=0)
    shows             = models.PositiveSmallIntegerField(default=1)
    reachable         = models.PositiveIntegerField(null=True, blank=True,
                        help_text='People this show could plausibly reach, per show')

    fit               = models.PositiveSmallIntegerField(default=0,
                        verbose_name='Demographic and artistic fit')
    accessibility     = models.PositiveSmallIntegerField(default=0,
                        verbose_name='Geographic accessibility')
    affordability     = models.PositiveSmallIntegerField(default=0,
                        verbose_name='Ticket affordability')
    conversion        = models.PositiveSmallIntegerField(default=0,
                        verbose_name='Purchase conversion')
    competition       = models.PositiveSmallIntegerField(default=0,
                        verbose_name='Competition intensity')

    updated_at        = models.DateTimeField(auto_now=True)

    @property
    def total_capacity(self):
        return self.seats_per_show * max(1, self.shows)

    @property
    def estimated_attendance(self):
        """Reachable, narrowed by each filter in turn. Returns None rather
        than a guess when the reachable audience has not been entered, because
        a made up denominator is worse than an empty cell."""
        if not self.reachable:
            return None
        pct = 1.0
        for v in (self.fit, self.accessibility, self.affordability,
                  self.conversion, self.competition):
            pct *= (v or 0) / 100.0
        return int(self.reachable * max(1, self.shows) * pct)

    @property
    def occupancy(self):
        est, cap = self.estimated_attendance, self.total_capacity
        if not est or not cap:
            return None
        return round(100.0 * est / cap, 1)


class Contact(TenantOwned):
    """A person, not a company.

    A supplier is an organisation; the people inside it change. Someone moves
    from one firm to another and takes the relationship with them, so the
    person is kept in their own right and their employer is just a link that
    can be changed. Two contacts may share a name and still be two people.

    The invitation level is the guest list: whether this person gets asked to
    an event at all, and at what level.
    """

    KIND = [
        ('accommodation', 'Accommodation'), ('artist_mgmt', 'Artist & Management'),
        ('guest', 'Guest'), ('health_safety', 'Health & Safety'),
        ('hospitality', 'Hospitality & Catering'), ('legal_finance', 'Legal & Finance'),
        ('media', 'Media & Press'), ('production', 'Production & Crew'),
        ('venue', 'Venue'), ('other', 'Other'),
    ]
    INVITE = [('', 'Not invited'), ('vip', 'VIP'), ('general', 'General')]

    name       = models.CharField(max_length=160)
    kind       = models.CharField(max_length=20, choices=KIND, default='other')
    invitation = models.CharField(max_length=10, choices=INVITE, blank=True, default='')
    role       = models.CharField(max_length=120, blank=True, default='')
    phone      = models.CharField(max_length=40, blank=True, default='')
    email      = models.EmailField(blank=True, default='')
    city       = models.CharField(max_length=120, blank=True, default='')
    country    = models.CharField(max_length=80, blank=True, default='')

    # Where they work now. Changing this does not rewrite history: past events
    # keep whichever company was engaged at the time.
    supplier   = models.ForeignKey(Supplier, on_delete=models.SET_NULL, null=True,
                 blank=True, related_name='contacts')
    partner    = models.ForeignKey(Partner, on_delete=models.SET_NULL, null=True,
                 blank=True, related_name='contacts')
    venue      = models.ForeignKey(Venue, on_delete=models.SET_NULL, null=True,
                 blank=True, related_name='contacts')
    artist     = models.ForeignKey(Artist, on_delete=models.SET_NULL, null=True,
                 blank=True, related_name='contacts')

    note       = models.TextField(blank=True, default='')
    is_active  = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name


class EventAssignment(models.Model):
    """Who is working on this event.

    Operations sees only the events they are assigned to, so this is not a
    convenience, it is the access rule. Without a row here, an Operations user
    cannot reach the event at all, and the money never reaches the page for
    them whatever their assignment.
    """

    event      = models.ForeignKey(Event, on_delete=models.CASCADE,
                 related_name='assignments')
    user       = models.ForeignKey('accounts.User', on_delete=models.CASCADE,
                 related_name='veventa_assignments')
    role       = models.CharField(max_length=120, blank=True, default='',
                 help_text='What they are doing on this one, for the agenda')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = [('event', 'user')]
        ordering = ['id']


class CompanyAccess(models.Model):
    """Which companies a user may see.

    An Event Specialist is scoped to their own company. A tenant that runs
    more than one trading entity must be able to keep them apart, and the
    filter belongs here rather than in each view, where it would eventually be
    forgotten on one page and leak everything.
    """

    company    = models.ForeignKey(Company, on_delete=models.CASCADE,
                 related_name='user_access')
    user       = models.ForeignKey('accounts.User', on_delete=models.CASCADE,
                 related_name='veventa_companies')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = [('company', 'user')]
        verbose_name_plural = 'Company access'
