class BackendSponsorshipLevelForm(BackendForm):
helplink = 'sponsors#level'
- list_fields = ['levelname', 'levelcost', 'available', 'public', ]
+ list_fields = ['levelname', 'levelcost', 'available', 'public', 'contractlevel']
linked_objects = OrderedDict({
'benefit': BackendSponsorshipLevelBenefitManager(),
})
class Meta:
model = SponsorshipLevel
- fields = ['levelname', 'urlname', 'levelcost', 'available', 'public', 'maxnumber', 'instantbuy',
+ fields = ['levelname', 'urlname', 'levelcost', 'available', 'public', 'maxnumber', 'contractlevel',
'paymentdays', 'paymentdueby', 'paymentmethods', 'invoiceextradescription', 'contract', 'canbuyvoucher', 'canbuydiscountcode']
widgets = {
'paymentmethods': django.forms.CheckboxSelectMultiple,
{
'id': 'contract',
'legend': 'Contract information',
- 'fields': ['instantbuy', 'contract', 'paymentdays', 'paymentdueby'],
+ 'fields': ['contractlevel', 'contract', 'paymentdays', 'paymentdueby'],
},
{
'id': 'payment',
def clean(self):
cleaned_data = super(BackendSponsorshipLevelForm, self).clean()
- if not (cleaned_data.get('instantbuy', False) or cleaned_data['contract']):
- self.add_error('instantbuy', 'Sponsorship level must either be instant signup or have a contract')
- self.add_error('contract', 'Sponsorship level must either be instant signup or have a contract')
-
- if int(cleaned_data['levelcost'] == 0) and cleaned_data.get('instantbuy', False):
- self.add_error('levelcost', 'Sponsorships with zero cost can not be instant signup')
- self.add_error('instantbuy', 'Sponsorships with zero cost can not be instant signup')
+ if cleaned_data['contractlevel'] == 0:
+ if cleaned_data['contract']:
+ self.add_error('contract', 'Contracts cannot be specified when contract level is No contract')
+ if cleaned_data['levelcost'] == 0:
+ self.add_error('levelcost', 'Cost cannot be zero when contract level is No contract')
+ elif cleaned_data['contractlevel'] == 1:
+ if not cleaned_data['contract']:
+ self.add_error('contract', 'Contract is required when contract level is Click-through')
+ if cleaned_data['levelcost'] == 0:
+ self.add_error('levelcost', 'Cost cannot be zero when contract level is Click-through')
+ else:
+ if not cleaned_data['contract']:
+ self.add_error('contract', 'Contract is required when contract level is Full')
return cleaned_data
--- /dev/null
+# Generated by Django 4.2.11 on 2025-02-26 20:22
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('confsponsor', '0033_payment_terms'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='sponsorshiplevel',
+ name='contractlevel',
+ field=models.IntegerField(choices=[(0, 'No contract'), (1, 'Click-through contract'), (2, 'Full contract')], default=0, verbose_name='Contract level'),
+ ),
+ migrations.RunSQL("""
+UPDATE confsponsor_sponsorshiplevel SET contractlevel=CASE
+WHEN instantbuy AND contract_id IS NOT NULL THEN 1
+WHEN instantbuy AND contract_id IS NULL THEN 0
+ELSE 2 END""",
+ """
+UPDATE confsponsor_sponsorshiplevel SET instantbuy=(contractlevel < 2)"""),
+ migrations.RemoveField(
+ model_name='sponsorshiplevel',
+ name='instantbuy',
+ ),
+ ]
(2, 'Company is from outside EU'),
)
+CONTRACT_LEVEL_CHOICES = (
+ (0, 'No contract'),
+ (1, 'Click-through contract'),
+ (2, 'Full contract'),
+)
+CONTRACT_LEVEL_MAP = dict(CONTRACT_LEVEL_CHOICES)
+
class SponsorshipContract(models.Model):
conference = models.ForeignKey(Conference, null=False, blank=False, on_delete=models.CASCADE)
public = models.BooleanField(null=False, blank=False, default=True, verbose_name="Publicly visible",
help_text="If unchecked the sponsorship level will be treated as internal, for example for testing")
maxnumber = models.IntegerField(null=False, blank=False, default=0, verbose_name="Maximum number of sponsors")
- instantbuy = models.BooleanField(null=False, blank=False, default=False, verbose_name="Instant buy available")
+ contractlevel = models.IntegerField(null=False, blank=False, default=0, verbose_name="Contract level",
+ choices=CONTRACT_LEVEL_CHOICES)
paymentmethods = models.ManyToManyField(InvoicePaymentMethod, blank=False, verbose_name="Payment methods for generated invoices")
invoiceextradescription = models.TextField(
blank=True, null=False, verbose_name="Invoice extra description",
return True
return False
+ @cached_property
+ def contractlevel_name(self):
+ return CONTRACT_LEVEL_MAP[self.contractlevel]
+
+ def _display_contractlevel(self, cache):
+ return self.contractlevel_name
+
class SponsorshipBenefit(models.Model):
level = models.ForeignKey(SponsorshipLevel, null=False, blank=False, on_delete=models.CASCADE)
'cost': format_currency(lvl.levelcost),
'available': lvl.available,
'maxnumber': lvl.maxnumber,
- 'instantbuy': lvl.instantbuy,
+ 'instantbuy': lvl.contractlevel == 1 or (lvl.contractlevel == 0 and cost > 0), # legacy
+ 'contractlevel': lvl.contractlevel_name,
'benefits': [dict(_get_benefit_data(b)) for b in lvl.sponsorshipbenefit_set.all()
],
}
# Should not happen
raise Http404("No contract at this level")
- if sponsor.level.instantbuy:
+ if sponsor.level.contractlevel == 1:
# Click-through contract
resp = HttpResponse(content_type='application/pdf')
conference = sponsor.conference
level = sponsor.level
+ if level.contractlevel == 0:
+ return
+
pdf = fill_pdf_fields(
level.contract.contractpdf,
get_pdf_fields_for_conference(conference, sponsor),
send_sponsor_manager_email(
sponsor,
'Your contract for {}'.format(conference.conferencename),
- 'confsponsor/mail/{}.txt'.format('sponsor_contract_instant' if level.instantbuy else 'sponsor_contract_manual'),
+ 'confsponsor/mail/{}.txt'.format('sponsor_contract_instant' if level.contractlevel == 1 else 'sponsor_contract_manual'),
{
'conference': conference,
'sponsor': sponsor,
# Stage 2 = contract choice. When submitted, sign up.
# If there is no contract needed on this level, or there is no choice
# of contract because only one available, we bypass stage 1.
- if stage == '1' and (level.instantbuy or not conference.contractprovider or not conference.manualcontracts):
+ if stage == '1' and (level.contractlevel != 2 or not conference.contractprovider or not conference.manualcontracts):
stage = '2'
def _render_contract_choices():
'level': level,
'form': form,
'noform': 1,
- 'needscontract': not (level.instantbuy or not conference.contractprovider),
'sponsorname': form.cleaned_data['name'],
'vatnumber': form.cleaned_data['vatnumber'] if settings.EU_VAT else None,
'previewaddr': get_sponsor_invoice_address(form.cleaned_data['name'],
# If the Continue editing button is selected we should go back
# to just rendering the normal form. Otherwise, go ahead and create the record.
if request.POST.get('submit', '') != 'Continue editing':
- if request.POST.get('contractchoice', '') not in ('0', '1') and not level.instantbuy:
+ if request.POST.get('contractchoice', '') not in ('0', '1') and level.contractlevel == 2:
return _render_contract_choices()
social = {
level=level,
social=social,
invoiceaddr=form.cleaned_data['address'],
- signmethod=1 if request.POST.get('contractchoice', '') == '1' or not conference.contractprovider or level.instantbuy else 0,
+ signmethod=1 if request.POST.get('contractchoice', '') == '1' or not conference.contractprovider or level.contractlevel < 2 else 0,
autoapprovesigned=conference.autocontracts,
)
if settings.EU_VAT:
error = None
- if level.instantbuy:
- if sponsor.level.contract:
- # Instantbuy levels that has a contract should get an implicit contract
- # attached to an email.
+ if level.contractlevel < 2:
+ # No contract or click-through contract
+ if level.contractlevel == 1:
+ # Click-through contract
_generate_and_send_sponsor_contract(sponsor)
mailstr += "Level does not require a signed contract. Verify the details and approve\nthe sponsorship using:\n\n{0}/events/sponsor/admin/{1}/{2}/".format(
{%else%}
<span class="label label-success">Invoiced</span>
{%endif%}
- {%elif s.level.instantbuy%}
+ {%elif s.level.contractlevel < 2 %}
<span class="label label-warning" title="Sponsor details for instant buy levels have to be verified before invoice is issued">Pending organizer verification</span>
{%else%}
{%if s.signmethod == 0 and s.contract %}
<p>
This sponsorship is awaiting an <a href="/invoiceadmin/{{sponsor.invoice.pk}}/">invoice</a> to be paid.
</p>
-{%if not sponsor.level.instantbuy%}
-{%comment%}Instant buy sponsorships should never be manually confirmed{%endcomment%}
+{%if sponsor.level.contractlevel == 2 %}
+{%comment%}Only full contract sponsorships should be manually confirmed{%endcomment%}
<p>
<b>Iff</b> there is a signed <i>and</i> countersigned contract available
for this sponsor, it can be confirmed before the invoice is paid.
{%else%}
{%comment%}Sponsor has no invoice{%endcomment%}
{%if sponsor.level.levelcost %}
-{%if sponsor.level.instantbuy%}
+{%if sponsor.level.contractlevel < 2%}
<p>
This sponsorship has not yet been issued an invoice. This is an
"instant buy" level sponsorship, so as soon as the sponsorship
has been received, go ahead and generate the invoice.
</p>
{%endif%}{%comment%}Digital contracts{%endcomment%}
-{%endif%}{%comment%}Instant buy{%endcomment%}
+{%endif%}{%comment%}contractlevel < 2{%endcomment%}
{%else%}{%comment%}levelcost != 0 {%endcomment%}
<p>
This sponsorship has zero cost, which means payment is handled manually.
<tr>
<th>Contract:</th>
<td>
- {%if sponsor.level.instantbuy %}
- {%if sponsor.level.contract %}
- Click-through contract completed. <form class="inline-block-form" method="post" action="resendcontract/">{% csrf_token %}<input type="submit" class="btn btn-sm btn-default confirm-btn" value="Re-send contract anyway" data-confirm="Are you sure you want to re-send a new contract to this sponsor?{%if sponsor.signmethod == 0%} {{conference.contractprovider.implementation.resendprompt}}{%endif%}"></form>
- {%else%}
+ {%if sponsor.level.contractlevel == 0 %}
No contract needed for this level.
- {%endif%}
- {%else%}
+ {%elif sponsor.level.contractlevel == 1 %}
+ Click-through contract completed. {%if not sponsor.confirmed%}<form class="inline-block-form" method="post" action="resendcontract/">{% csrf_token %}<input type="submit" class="btn btn-sm btn-default confirm-btn" value="Re-send contract anyway" data-confirm="Are you sure you want to re-send a new contract to this sponsor?{%if sponsor.signmethod == 0%} {{conference.contractprovider.implementation.resendprompt}}{%endif%}"></form>{%endif%}
+ {%else%}{%comment%}Full contract{%endcomment%}
{%if sponsor.signmethod == 0%}
Digital contract.<br/>
{%if sponsor.contract.completed%}Signed ({{sponsor.contract.firstsigned}}) and countersigned ({{sponsor.contract.completed}}).
<form class="inline-block-form" method="post" action="resendcontract/">{% csrf_token %}<input type="submit" class="btn btn-sm btn-default confirm-btn" value="Re-send contract" data-confirm="Are you sure you want to re-send a new contract to this sponsor?{%if sponsor.signmethod == 0%} {{conference.contractprovider.implementation.resendprompt}}{%endif%}"></form>
{%endif%}
{%endif%}{# can resend #}
- {%endif%}{# instant buy #}
+ {%endif%}{# contractlevel #}
</td>
</tr>
{%if sponsor.invoice%}
form below to initiate your sponsorship!
</p>
-{%if level.contract%}
-<p>
-Before you complete the signup form, please <em>make sure</em> you have read the
-<a href="/events/sponsor/previewcontract/{{level.contract.id}}/">contract</a>, and
-agree with the contents in it.
-</p>
-{%endif%}
-
{%if form.errors%}
<p>
<b>NOTE!</b> Your submitted form contained errors and has <b>not</b> been saved!
</div>
{%if previewaddr%}
-{%if needscontract%}
+{%if level.contractlevel > 0%}
<h4>Contract details</h4>
+{% if level.contractlevel == 1 %}
+<p>
+ This sponsorship level uses a <strong>click-through contract</strong>. This
+ means that by signing up, you agree to the
+ <a href="/events/sponsor/previewcontract/{{level.contract.id}}/">contract</a>, and
+ accept that no changes can be made to it.
+</p>
+{% else %}
+<p>
+ This sponsorship level requires a signed contract. Please review the
+ <a href="/events/sponsor/previewcontract/{{level.contract.id}}/">contract</a> before
+ completing the sign-up, and <em>only continue if you agree with it</em>.
+ Once signed up, you will receive a contract or signing.
+</p>
+{% endif %}
<p>
The contract for the sponsorship will be issued to the company name
<strong><em>{{sponsorname}}</em></strong>{%if vatnumber%} with VAT
number <strong><em>{{vatnumber}}</em></strong>{%endif%}.
-</p>
-<p>
- It will <strong>not</strong> be possible to change this after this
- step, so if anything about it is incorrect please click <i>Continue
+
+ It will <strong>not</strong> be possible to change this, or any other
+ details in the contract, after this
+ step. If anything about it is incorrect please click <i>Continue
editing</i> and correct it <i>before</i> proceeding.
</p>
{%endif%}
<input type="hidden" name="stage" value="2">
{%endif%}{# contractchoices #}
-{%if not level.instantbuy and not noform %}
+{%if level.contractlevle == 2 and not noform %}
<p>
Please note that due to the level of this sponsorship contract, we
will require a signed contract, apart from the confirmation
<th>Status:</th>
<td>{%if sponsor.confirmed%}Confirmed ({{sponsor.confirmedat}}){%else%}<i>Awaiting confirmation</i>{%endif%}</td>
</tr>
-{% if sponsor.confirmed and sponsor.level.contract %}
+{% if sponsor.confirmed %}
<tr>
<th>Contract:</th>
<td>
-{% if sponsor.level.instantbuy %}
+{% if sponsor.level.contractlevel == 0 %}
+This level requires no contract.
+{% elif sponsor.level.contractlevel == 1 %}
Click-through contract agreed to. <a href="contractview/" class="btn btn-outline-dark btn-sm">View copy of contract</a>
{% else %}
{%if sponsor.signmethod == 0%}
Digital contract completed {{sponsor.contract.completed}}.
{%if sponsor.contract.completed and sponsor.contract.has_completed_pdf %}<a href="contractview/" class="btn btn-outline-dark btn-sm">View signed contract</a>{%endif%}
{% else %}
-Manual contract.
-{% endif %}
-{% endif %}
+Manual contract signed.
+{% endif %}{%comment%}digital contract/manual contract{%endcomment%}
+{% endif %}{%comment%}contractlevel{%endcomment%}
</td>
</tr>
{% endif %}
{%endwith%}
{%else%}
{%comment%}No invoice generated{%endcomment%}
-{%if sponsor.level.instantbuy%}
-{%comment%}No invoice generated but instantbuy active, so awaiting admin approval{%endcomment%}
+{%if sponsor.level.contractlevel < 2 %}
+{%comment%}No invoice generated but clickthrough contract or no contract, so awaiting admin approval{%endcomment%}
<p>
Your sponsorship request has been submitted, and is currently awaiting confirmation
from the conference organizers. As soon as it has been, an invoice will be automatically