Add support for sponsors requesting explicit contract at click-through levels
authorMagnus Hagander <magnus@hagander.net>
Tue, 3 Jun 2025 13:38:52 +0000 (15:38 +0200)
committerMagnus Hagander <magnus@hagander.net>
Tue, 3 Jun 2025 13:38:52 +0000 (15:38 +0200)
Click-through contracts makes it easier for most, but some sponsor
organisations require an actual contract at these levels as well. To
handle this, add an option during contract/address verification to
request an explicit contract, which can then be either manual or
digital, and will both prevent the clickthrough contract and invoice to
be sent until thsi contract has been completed.

postgresqleu/confsponsor/migrations/0035_sponsor_explicitcontract.py [new file with mode: 0644]
postgresqleu/confsponsor/models.py
postgresqleu/confsponsor/util.py
postgresqleu/confsponsor/views.py
template/confsponsor/admin_dashboard.html
template/confsponsor/admin_sponsor.html
template/confsponsor/admin_sponsor_details.html
template/confsponsor/signupform.html
template/confsponsor/sponsor.html

diff --git a/postgresqleu/confsponsor/migrations/0035_sponsor_explicitcontract.py b/postgresqleu/confsponsor/migrations/0035_sponsor_explicitcontract.py
new file mode 100644 (file)
index 0000000..7b53b36
--- /dev/null
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.11 on 2025-06-03 13:17
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('confsponsor', '0034_rename_instantbuy'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='sponsor',
+            name='explicitcontract',
+            field=models.BooleanField(default=False, verbose_name='Requested explicit contract'),
+        ),
+    ]
index d6d9b404c8a732b8ba55c8ec7b0d2a15fd911ec3..1e56c985baf178bc5308eda7d64af841bf893938 100644 (file)
@@ -166,6 +166,7 @@ class Sponsor(models.Model):
     signmethod = models.IntegerField(null=False, blank=False, default=1, choices=((0, 'Digital signatures'), (1, 'Manual signatures')), verbose_name='Signing method')
     autoapprovesigned = models.BooleanField(null=False, blank=False, default=True, verbose_name="Approve on signing", help_text="Automatically approve once digital signatures are completed")
     contract = models.OneToOneField(DigisignDocument, null=True, blank=True, help_text="Contract, when using digital signatures", on_delete=models.SET_NULL)
+    explicitcontract = models.BooleanField(null=False, blank=False, default=False, verbose_name='Requested explicit contract')
 
     def __str__(self):
         return self.name
index f9daeafac4e57ab8a99cc25128f57990cfa4c2d1..9e87617d86d92cb4c0143c12bc05dcf3646a4ae0 100644 (file)
@@ -175,7 +175,7 @@ def get_pdf_fields_for_conference(conference, sponsor=None, overrides={}):
         fields.append(
             ('static:euvat', sponsor.vatnumber if sponsor else overrides.get('static:euvat', 'Sponsor EU VAT number')),
         )
-    if sponsor and sponsor.level.contractlevel == 1:
+    if sponsor and sponsor.level.contractlevel == 1 and not sponsor.explicitcontract:
         # Only add clickthrough contract fields if it's a clickthrough level (or a preview, with no sponsor yet)
         fields.extend([
             ('static:clickthrough', overrides.get('static:clickthrough', 'Click-through agreement')),
index 09701e70be502d2b08759c8078e0ee1aeb8a4657..5b7cdae06a0db65022a2a315f435eaada42b41d1 100644 (file)
@@ -389,7 +389,7 @@ def _generate_and_send_sponsor_contract(sponsor):
         send_sponsor_manager_email(
             sponsor,
             'Your contract for {}'.format(conference.conferencename),
-            'confsponsor/mail/{}.txt'.format('sponsor_contract_instant' if level.contractlevel == 1 else 'sponsor_contract_manual'),
+            'confsponsor/mail/{}.txt'.format('sponsor_contract_instant' if (level.contractlevel == 1 and not sponsor.explicitcontract) else 'sponsor_contract_manual'),
             {
                 'conference': conference,
                 'sponsor': sponsor,
@@ -455,7 +455,7 @@ def sponsor_signup(request, confurlname, levelurlname):
         # 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.contractlevel != 2 or not conference.contractprovider or not conference.manualcontracts):
+        if stage == '1' and ((level.contractlevel != 2 and request.POST.get('explicitcontract', '') != '1') or not conference.contractprovider or not conference.manualcontracts):
             stage = '2'
 
         def _render_contract_choices():
@@ -480,6 +480,7 @@ def sponsor_signup(request, confurlname, levelurlname):
                 'form': form,
                 'noform': 1,
                 'contractchoices': contractchoices,
+                'explicitcontract': request.POST.get('explicitcontract', '') == '1',
             })
 
         if stage == '0':
@@ -507,7 +508,7 @@ def sponsor_signup(request, confurlname, levelurlname):
             # 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 level.contractlevel == 2:
+                if request.POST.get('contractchoice', '') not in ('0', '1') and (level.contractlevel == 2 or request.POST.get('explicitcontract', '') == '1'):
                     return _render_contract_choices()
 
                 social = {
@@ -525,8 +526,9 @@ def sponsor_signup(request, confurlname, levelurlname):
                                   level=level,
                                   social=social,
                                   invoiceaddr=form.cleaned_data['address'],
-                                  signmethod=1 if request.POST.get('contractchoice', '') == '1' or not conference.contractprovider or level.contractlevel < 2 else 0,
+                                  signmethod=1 if request.POST.get('contractchoice', '') == '1' or not conference.contractprovider or level.contractlevel < 2 or request.POST.get('explicitcontract', '') == '1' else 0,
                                   autoapprovesigned=conference.autocontracts,
+                                  explicitcontract=request.POST.get('explicitcontract', '') == '1',
                                   )
                 if settings.EU_VAT:
                     sponsor.vatstatus = int(form.cleaned_data['vatstatus'])
@@ -539,7 +541,7 @@ def sponsor_signup(request, confurlname, levelurlname):
 
                 error = None
 
-                if level.contractlevel < 2:
+                if level.contractlevel < 2 and request.POST.get('explicitcontract', '') != '1':
                     # No contract or click-through contract
                     if level.contractlevel == 1:
                         # Click-through contract
@@ -548,6 +550,7 @@ def sponsor_signup(request, confurlname, levelurlname):
                     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(
                         settings.SITEBASE, conference.urlname, sponsor.id)
                 else:
+                    # Contract required!
                     contractid, error = _generate_and_send_sponsor_contract(sponsor)
 
                     if sponsor.signmethod == 1:
index 8fa6f1198d11a30d94c8f7bb64e7e3adcfda5a85..e7d0ae8982665f5b847bc0e1110b3e16bd3b7245 100644 (file)
@@ -88,7 +88,7 @@ sponsor manually, you may want to confirm them manually as well...
         {%else%}
       <span class="label label-success">Invoiced</span>
         {%endif%}
-      {%elif s.level.contractlevel < 2 %}
+      {%elif s.level.contractlevel < 2 and not s.explicitcontract %}
       <span class="label label-warning" title="Sponsor details for {%if s.level.contractlevel == 1 %}click-through{%else%}no{%endif%} contract levels have to be verified before invoice is issued">Pending organizer verification</span>
       {%else%}
         {%if s.signmethod == 0 and s.contract %}
index 99b3caf737f9914bd7e01dc9a35440a10f3cc959..75ae0a8358772f2b3c9cef23d37c83aeca9f60ed 100644 (file)
 <p>
 This sponsorship is awaiting an <a href="/invoiceadmin/{{sponsor.invoice.pk}}/">invoice</a> to be paid.
 </p>
-{%if sponsor.level.contractlevel == 2 %}
+{%if sponsor.level.contractlevel == 2 or sponsor.explicitcontract %}
 {%comment%}Only full contract sponsorships should be manually confirmed{%endcomment%}
 <p>
 <b>Iff</b> there is a signed <i>and</i> countersigned contract available
@@ -161,7 +161,7 @@ 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.contractlevel < 2%}
+{%if sponsor.level.contractlevel < 2 and not sponsor.explicitcontract %}
 <p>
   This sponsorship has not yet been issued an invoice. This is a
 {%if sponsor.level.contractlevel == 1 %}click-through{%else%}no{%endif%}
@@ -192,7 +192,7 @@ This sponsorship has not yet been issued an invoice. Once the contract
 has been received, go ahead and generate the invoice.
 </p>
 {%endif%}{%comment%}Digital contracts{%endcomment%}
-{%endif%}{%comment%}contractlevel < 2{%endcomment%}
+{%endif%}{%comment%}contractlevel < 2 and not explicitcontract{%endcomment%}
 {%else%}{%comment%}levelcost != 0 {%endcomment%}
 <p>
   This sponsorship has zero cost, which means payment is handled manually.
index dac8b191b88405b748816f990b3137c4a27516fa..deacbc8c8a1cf0d4ec480d2b5ef51b577395647b 100644 (file)
@@ -41,7 +41,7 @@
     <td>
       {%if sponsor.level.contractlevel == 0 %}
       No contract needed for this level.
-      {%elif sponsor.level.contractlevel == 1 %}
+      {%elif sponsor.level.contractlevel == 1 and not sponsor.explicitcontract %}
       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%}
index 689e4d1ba9436a17d80102117bcf287e047fc680..684a37dc56f88c84b899ac9841bd9c18eed01754 100644 (file)
@@ -65,6 +65,19 @@ the invoice cannot be changed.
   step. If anything about it is incorrect please click <i>Continue
   editing</i> and correct it <i>before</i> proceeding.
 </p>
+
+{% if level.contractlevel == 1 %}
+<h4>Contract options</h4>
+<p>
+  As this level uses a click-thruogh contract, no specific contract signature is
+  required. However, if your organization requires a signed contract, you can
+  explicitly request one here.
+</p>
+<p>
+  <input type="checkbox" name="explicitcontract" value="1"> Request explicit contract
+</p>
+{% endif %}
+
 {%endif%}
 
 <h4>Invoice address preview</h4>
@@ -97,6 +110,7 @@ the invoice cannot be changed.
 </dl>
 
 <input type="hidden" name="stage" value="2">
+{%if explicitcontract %}<input type="hidden" name="explicitcontract" value="1">{%endif%}
 {%endif%}{# contractchoices #}
 
 {%if level.contractlevle == 2 and not noform %}
index 9f11f93457f0a7a25d309ce96eb47fefd6bfc312..6a4679aee92e153ce28bf76c09c0151cf633a904 100644 (file)
@@ -40,7 +40,7 @@
     <td>
 {% if sponsor.level.contractlevel == 0 %}
 This level requires no contract.
-{% elif sponsor.level.contractlevel == 1 %}
+{% elif sponsor.level.contractlevel == 1 and not sponsor.explicitcontract %}
       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%}
@@ -75,7 +75,7 @@ Manual contract signed.
 {%endwith%}
 {%else%}
 {%comment%}No invoice generated{%endcomment%}
-{%if sponsor.level.contractlevel < 2 %}
+{%if sponsor.level.contractlevel < 2 and not sponsor.explicitcontract %}
 {%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