From d507653a0bb6bc68216a8acf61c2394a4d4e3f8d Mon Sep 17 00:00:00 2001
From: arek <arek.gorzawski@ess.eu>
Date: Tue, 22 Oct 2024 13:55:53 +0200
Subject: [PATCH 1/8] New codes and way to see them WIP regarding #82 and #103

---
 shifts/hrcodes.py                            | 45 ++++++++++++++++++--
 shifts/migrations/0026_auto_20241017_1659.py | 27 ++++++++++++
 shifts/models.py                             |  4 ++
 shifts/static/js/user_space.js               | 36 ++++++++--------
 shifts/templates/shift_edit.html             |  1 +
 shifts/templates/shiftexchange_edit.html     |  8 +++-
 shifts/templates/user.html                   | 45 ++++++++++++++++----
 shifts/views/ajax.py                         | 30 ++++++++++++-
 shifts/views/main.py                         | 21 ++++++++-
 tests/test_hrcodes.py                        |  2 +-
 10 files changed, 183 insertions(+), 36 deletions(-)
 create mode 100644 shifts/migrations/0026_auto_20241017_1659.py

diff --git a/shifts/hrcodes.py b/shifts/hrcodes.py
index ae08d59..f62c90e 100644
--- a/shifts/hrcodes.py
+++ b/shifts/hrcodes.py
@@ -12,7 +12,6 @@ HANDOVER_IN_HOURS = 0.25
 NWH_DAY_DURATION_IN_HOURS = 7.8
 VACATION_DAY_IN_HOURS = 8.0
 
-
 # The ones that are counted as OB3 (as weekends)
 # TODO think of importing it as external table (not need to re-release it)
 # TODO consider https://pypi.org/project/holidays/ when Sweden is included (not in Nov 2021)
@@ -39,7 +38,9 @@ red_days = [
     date(2024, 12, 31),
 ]
 
-reduced_days = [
+#
+# These, with the new 2024-09-13 agrement will be treaded special bank/pay
+reduced_days_and_bridge = [
     date(2021, 1, 5),
     date(2021, 4, 1),
     date(2021, 4, 30),
@@ -112,7 +113,18 @@ def get_public_holidays(fmt=None):
 
 
 def count_total(counts):
-    countsReturn = {"OB1": 0, "OB2": 0, "OB3": 0, "OB4": 0, "NWH": 0}
+    countsReturn = {
+        "OB1": 0,
+        "OB2": 0,
+        "OB3": 0,
+        "OB4": 0,
+        "NWH": 0,
+        "ELC": 0,
+        "LC1": 0,
+        "LC2": 0,
+        "OT": 0,
+        "BTPorBTB": 0,
+    }
     for oneDay in counts.keys():
         for oneCode in countsReturn.keys():
             countsReturn[oneCode] += counts.get(oneDay)[oneCode]
@@ -150,7 +162,18 @@ def get_code_counts(shift: Shift, granulation=900, returnFactor=4, handoverTime=
     MAX_HOURS = 8
     if verbose:
         print("========================================\n =======< {} {} {} >=========".format(shift.date, shift.date.strftime("%A"), shift.slot))
-    counts = {"OB1": 0, "OB2": 0, "OB3": 0, "OB4": 0, "NWH": 0}
+    counts = {
+        "OB1": 0,
+        "OB2": 0,
+        "OB3": 0,
+        "OB4": 0,
+        "NWH": 0,
+        "ELC": 0,
+        "LC1": 0,
+        "LC2": 0,
+        "OT": 0,
+        "BTPorBTB": 0,
+    }
     duration = shift.end - shift.start
 
     if verbose:
@@ -178,6 +201,20 @@ def get_code_counts(shift: Shift, granulation=900, returnFactor=4, handoverTime=
     if verbose:
         print("Weekends OB3: ", counts)
 
+    for bd in reduced_days_and_bridge:
+        if shift.date == bd:
+            counts["BDTorBDB"] = 3 + handoverTime  # TODO or 8 check with HO
+
+    if shift.is_changed:
+        counts["ELC"] = 1  # TODO check unit
+        if shift.is_changed_urgent:
+            counts["LC1"] = 1
+        else:
+            counts["LC2"] = 1
+        if shift.is_changed_offday:
+            counts["OT"] = 8.25  # TODO check
+            notAWEOrHoliday = False
+
     if notAWEOrHoliday:
         _check(shift, "NWH", counts)
         _check(shift, "OB1", counts)
diff --git a/shifts/migrations/0026_auto_20241017_1659.py b/shifts/migrations/0026_auto_20241017_1659.py
new file mode 100644
index 0000000..2aea74f
--- /dev/null
+++ b/shifts/migrations/0026_auto_20241017_1659.py
@@ -0,0 +1,27 @@
+# Generated by Django 3.2.16 on 2024-10-17 14:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("shifts", "0025_shift_is_vacation"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="shift",
+            name="is_changed",
+            field=models.BooleanField(default=False, help_text="Applies for change compensation?"),
+        ),
+        migrations.AddField(
+            model_name="shift",
+            name="is_changed_offday",
+            field=models.BooleanField(default=False, help_text="Applies for OT instead monetary?"),
+        ),
+        migrations.AddField(
+            model_name="shift",
+            name="is_changed_urgent",
+            field=models.BooleanField(default=False, help_text="Applies for extra change compensation?"),
+        ),
+    ]
diff --git a/shifts/models.py b/shifts/models.py
index dfff95d..df9039a 100644
--- a/shifts/models.py
+++ b/shifts/models.py
@@ -218,6 +218,10 @@ class Shift(models.Model):
     is_cancelled = models.BooleanField(default=False, help_text="Last minute cancellation? IT IS counted for the HR " "code.")
     is_active = models.BooleanField(default=True, help_text="Leave or early cancellation, IT IS NOT counted for the " "HR code.")
     is_vacation = models.BooleanField(default=False)
+    # The following should be only for ROTA/ADMIN
+    is_changed = models.BooleanField(default=False, help_text="Applies for change compensation?")
+    is_changed_urgent = models.BooleanField(default=False, help_text="Applies for extra change compensation?")
+    is_changed_offday = models.BooleanField(default=False, help_text="Applies for OT instead monetary?")
 
     pre_comment = models.TextField(blank=True, null=True, help_text="Text to be displayed in shifters as shift constraint. [Shifters view " "ONLY]")
     post_comment = models.TextField(blank=True, null=True, help_text="Summary/comment for the end/post shift time. [Shifters view ONLY]")
diff --git a/shifts/static/js/user_space.js b/shifts/static/js/user_space.js
index 6690050..c85c962 100644
--- a/shifts/static/js/user_space.js
+++ b/shifts/static/js/user_space.js
@@ -54,14 +54,14 @@ $(document).ready(function() {
             };
 
             // Total over all pages
-            ob1 = api.column(1)
+            ob1 = api.column(3)
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
 
             ob1Total = api
-                .column(1, {
+                .column(3, {
                     page: 'current'
                 })
                 .data()
@@ -69,14 +69,14 @@ $(document).ready(function() {
                     return intVal(a) + intVal(b);
                 }, 0);
 
-            ob2 = api.column(2)
+            ob2 = api.column(4)
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
 
             ob2Total = api
-                .column(2, {
+                .column(4, {
                     page: 'current'
                 })
                 .data()
@@ -84,14 +84,14 @@ $(document).ready(function() {
                     return intVal(a) + intVal(b);
                 }, 0);
 
-            ob3 = api.column(3)
+            ob3 = api.column(5)
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
 
             ob3Total = api
-                .column(3, {
+                .column(5, {
                     page: 'current'
                 })
                 .data()
@@ -100,14 +100,14 @@ $(document).ready(function() {
                 }, 0);
 
 
-            ob4 = api.column(4)
+            ob4 = api.column(6)
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
 
             ob4Total = api
-                .column(4, {
+                .column(6, {
                     page: 'current'
                 })
                 .data()
@@ -115,14 +115,14 @@ $(document).ready(function() {
                     return intVal(a) + intVal(b);
                 }, 0);
 
-            nwh = api.column(5)
+            nwh = api.column(2)
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
 
             nwhTotal = api
-                .column(5, {
+                .column(2, {
                     page: 'current'
                 })
                 .data()
@@ -130,25 +130,25 @@ $(document).ready(function() {
                     return intVal(a) + intVal(b);
                 }, 0);
 
-            vac = api.column(6)
+            vac = api.column(1)
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
 
-            vacTotal = api.column(6, { page: 'current' })
+            vacTotal = api.column(1, { page: 'current' })
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
 
             // Update footer
-            $(api.column(1).footer()).html(ob1Total + ' hours (' + ob1 + 'h total)');
-            $(api.column(2).footer()).html(ob2Total + ' hours (' + ob2 + 'h total)');
-            $(api.column(3).footer()).html(ob3Total + ' hours (' + ob3 + 'h total)');
-            $(api.column(4).footer()).html(ob4Total + ' hours (' + ob4 + 'h total)');
-            $(api.column(5).footer()).html(nwhTotal + ' hours (' + nwh + 'h total)');
-            $(api.column(6).footer()).html(vacTotal + ' days (' + vac + 'd total)');
+            $(api.column(1).footer()).html(ob1Total + 'd (' + vac + 'd)');
+            $(api.column(2).footer()).html(ob2Total + 'h (' + nwh + 'h)');
+            $(api.column(3  ).footer()).html(ob3Total + 'h (' + ob1 + 'h)');
+            $(api.column(4).footer()).html(ob4Total + 'h (' + ob2 + 'h)');
+            $(api.column(5).footer()).html(nwhTotal + 'h (' + ob3 + 'h)');
+            $(api.column(6).footer()).html(vacTotal + 'h (' + ob4 + 'h)');
             var total = (ob1/600 + ob2/450 + ob3/300 + ob4/150) * 100;
             $("#helpOB").html(total.toFixed(1)+'%');
         },
diff --git a/shifts/templates/shift_edit.html b/shifts/templates/shift_edit.html
index 5cb8767..e4db97a 100644
--- a/shifts/templates/shift_edit.html
+++ b/shifts/templates/shift_edit.html
@@ -7,6 +7,7 @@
         <div class="alert alert-primary" role="alert">
         <h5>Edit/Update shift of </h5>
         <h2 class="display-5">{{ shift }}</h2>
+            <p>{{shift.revision}}</p>
         </div>
         {% if request.user.is_staff and edit %}
         <form action="{% url 'shifter:shift-single-exchange-post' shift.id %}" method="POST" enctype="multipart/form-data" class="form-horizontal">
diff --git a/shifts/templates/shiftexchange_edit.html b/shifts/templates/shiftexchange_edit.html
index 96a92b5..fec44a4 100644
--- a/shifts/templates/shiftexchange_edit.html
+++ b/shifts/templates/shiftexchange_edit.html
@@ -15,9 +15,13 @@
             <p class="card-text">The follwoing shifts were exchanged:</p>
             <hr>
             {% for onePair in sEx.shifts.all%}
-              <p> <small>[WAS]</small> {{onePair.shift}} <i class="fa-solid fa-arrows-left-right"></i>
+              <p> <small>[WAS]</small>
+                  <a class="btn btn-outline-secondary" href="{% url 'shifter:shift-view' onePair.shift.id %}">{{onePair.shift}}</a> <i class="fa-solid fa-arrows-left-right"></i>
                   <small>[IS now]</small> {{onePair.shift_for_exchange.member}}</p>
-            <p>[taker old shift] {{onePair.shift_for_exchange}}</p>
+            <p>[WAS] <a class="btn btn-outline-secondary" href="{% url 'shifter:shift-view' onePair.shift_for_exchange.id %}">{{onePair.shift_for_exchange}}</a>
+                <i class="fa-solid fa-arrows-left-right"></i>
+                  <small>[IS now]</small>  {{onePair.shift.member}}
+                <a class="btn btn-outline-success" href="{% url 'shifter:shift-view' newShift.id %}">{{newShift}}</a></p>
             <hr>
             {%endfor%}
           </div>
diff --git a/shifts/templates/user.html b/shifts/templates/user.html
index 31e7bbd..55618ea 100644
--- a/shifts/templates/user.html
+++ b/shifts/templates/user.html
@@ -110,7 +110,7 @@
                         <div class="row justify-content-center m-4">
                             <div class="col">
                                 <h2 class="mb-3">HR Codes to report</h2>
-                                <div class="alert alert-primary" role="alert">
+                                <div class="alert alert-success" role="alert">
                                   <h4 class="alert-heading">Nice top up!</h4>
                                     <hr>
                                   <p> For the selected period, you may find  <strong id="helpOB"></strong> more on your payslip due to your OBs! Enjoy!</p>
@@ -128,24 +128,48 @@
                                     </div>
                                 </form>
 
+                                <p>
+                                  <a class="btn btn-secondary" data-bs-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false" aria-controls="collapseExample">
+                                    Show HR codes explanation
+                                  </a>
+                                </p>
+                                <div class="collapse" id="collapseExample">
+                                  <div class="card card-body">
+                                    <ul>
+                                      <li><strong>VAC</strong>, your vacation days, report usig VAC (in days) or CL/BL (in hours) </li>
+                                      <li><strong>OB1/OB2/OB3/OB4</strong> - regular unsocial working hours, </li>
+                                      <li><strong>NWH</strong> - normal working hours, usually office time, conference, training etc. </li>
+                                      <li><strong>BD for: BTP or BTB</strong> - Bridging day time pay or time bank, to be reported with the personal preference,</li>
+                                      <li><strong>ELC</strong> -  one time (in 14days) entitlement for the List (Schedule) change,</li>
+                                      <li><strong>LC1/LC2</strong> - extra entiltement for a changed schedule, LC1 for if less than 2 days in advance, LC2 for day 3-14</li>
+                                        <li><strong>OT</strong> - Over time to be reported if the shift was done on a innitially free day. <strong>NOTE</strong> Mind reporting with the correct code: OTT1/2 or OTW1/2 depending on the time and preference.</li>
+                                    </ul>
+                                  </div>
+                                </div>
 
                                 <div class="table-responsive">
                                     <table class="table table-striped" id="table_HR" data-source="{% url 'ajax.get_hr_codes' %}">
                                         <thead>
                                             <tr>
                                                 <th scope="col">#</th>
-                                                <th scope="col">OB1 hours</th>
-                                                <th scope="col">OB2 hours</th>
-                                                <th scope="col">OB3 hours</th>
-                                                <th scope="col">OB4 hours</th>
-                                                <th scope="col">NWH hours</th>
-                                                <th scope="col">Vacation</th>
+                                                <th scope="col">Vac [d]</th>
+                                                <th scope="col">NWH [h]</th>
+                                                <th scope="col">OB1 [h]</th>
+                                                <th scope="col">OB2 [h]</th>
+                                                <th scope="col">OB3 [h]</th>
+                                                <th scope="col">OB4 [h]</th>
+                                                <th scope="col">BD [h]</th>
+                                                <th scope="col">ELC [1]</th>
+                                                <th scope="col">LC1 [1]</th>
+                                                <th scope="col">LC2 [1]</th>
+                                                <th scope="col">OT [h]</th>
                                             </tr>
                                         </thead>
                                         <tbody>
                                         <!-- filled by AJAX -->
                                         </tbody>
                                         <tfoot>
+                                        <!-- bottom of this table is filled by js-->
                                             <tr>
                                                 <th></th>
                                                 <th></th>
@@ -154,6 +178,11 @@
                                                 <th></th>
                                                 <th></th>
                                                 <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
                                             </tr>
                                         </tfoot>
                                     </table>
@@ -420,7 +449,7 @@
 
 {%  block js %}
 <script type="text/javascript" src="{% static 'js/calendar_script.min.js' %}"></script>
-<script type="text/javascript" src="{% static 'js/user_space.min.js' %}"></script>
+<script type="text/javascript" src="{% static 'js/user_space.js' %}"></script>
 <script type="text/javascript" src="{% static 'datatables.net/js/jquery.dataTables.min.js' %}"></script>
 <script type="text/javascript" src="{% static 'datatables.net-select/js/dataTables.select.min.js' %}"></script>
 <script type="text/javascript" src="{% static 'datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
diff --git a/shifts/views/ajax.py b/shifts/views/ajax.py
index 5722b9d..411eaee 100644
--- a/shifts/views/ajax.py
+++ b/shifts/views/ajax.py
@@ -266,11 +266,37 @@ def get_hr_codes(request: HttpRequest) -> HttpResponse:
 
     data = []
     for date, item in shift2codes.items():
-        a_row = [date, _trim4vis(item["OB1"]), _trim4vis(item["OB2"]), _trim4vis(item["OB3"]), _trim4vis(item["OB4"]), _trim4vis(item["NWH"]), ""]
+        a_row = [
+            date[2:],
+            "",  # vacation comes in the other line
+            _trim4vis(item["NWH"]),
+            _trim4vis(item["OB1"]),
+            _trim4vis(item["OB2"]),
+            _trim4vis(item["OB3"]),
+            _trim4vis(item["OB4"]),
+            _trim4vis(item["BTPorBTB"]),
+            _trim4vis(item["ELC"]),
+            _trim4vis(item["LC1"]),
+            _trim4vis(item["LC2"]),
+            _trim4vis(item["OT"]),
+        ]
         data.append(a_row)
 
     for date in dates:
-        a_row = [date, "", "", "", "", "", "1"]
+        a_row = [
+            date[2:],
+            "1",
+            "",
+            "",
+            "",
+            "",
+            "",
+            "",
+            "",
+            "",
+            "",
+            "",
+        ]
         data.append(a_row)
 
     return HttpResponse(json.dumps({"data": data}), content_type="application/json")
diff --git a/shifts/views/main.py b/shifts/views/main.py
index 7d7c646..f2b7294 100644
--- a/shifts/views/main.py
+++ b/shifts/views/main.py
@@ -273,7 +273,26 @@ def shiftExchangeRequestCancel(request, ex_id=None):
 @login_required
 def shiftExchangeView(request, ex_id=None):
     sEx = ShiftExchange.objects.get(id=ex_id)
-    context = {"sEx": sEx}
+    print(sEx.shifts)
+    r = Revision.objects.filter(valid=True).order_by("-number")[0]
+    print(r)
+
+    class Aaa:
+        id = 666
+
+    s = Aaa()
+    print(s.id)
+    for one in sEx.shifts.all():
+        print(one.shift)
+        print(one.shift_for_exchange)
+
+        # s = Shift.objects.get(revision=r,
+        #                       member=one.shift.member,
+        #                       slot=one.shift_for_exchange.slot,
+        #                       date=one.shift_for_exchange.date)
+        # print(s)
+
+    context = {"sEx": sEx, "oldShiftRequestor": one.shift, "oldShiftRequested": one.shift_for_exchange, "newShift": s}
     return render(request, "shiftexchange_edit.html", prepare_default_context(request, context))
 
 
diff --git a/tests/test_hrcodes.py b/tests/test_hrcodes.py
index 28f80c6..57b9ee3 100644
--- a/tests/test_hrcodes.py
+++ b/tests/test_hrcodes.py
@@ -173,4 +173,4 @@ class HRCodes(TestCase):
 
     def compare(self, codes1, codes2):
         for oneCode in codes1.keys():
-            self.assertEqual(codes1.get(oneCode), codes2.get(oneCode))
+            self.assertEqual(codes1.get(oneCode, 0), codes2.get(oneCode, 0))
-- 
GitLab


From 7bf97f5e647b3f32fd8b4f6bcc42c41798c8ffc1 Mon Sep 17 00:00:00 2001
From: arek <arek.gorzawski@ess.eu>
Date: Tue, 22 Oct 2024 20:37:50 +0200
Subject: [PATCH 2/8] WIP on #103

---
 shifts/hrcodes.py                        | 24 +++++++----
 shifts/templates/shift_edit.html         | 23 ++++++++++-
 shifts/templates/shift_edit_special.html | 43 ++++++++++++++++++++
 shifts/templates/shiftexchange_edit.html | 15 ++++---
 shifts/templates/team_desiderata.html    |  2 +-
 shifts/urls/main.py                      |  1 +
 shifts/views/desiderata.py               |  4 ++
 shifts/views/main.py                     | 52 +++++++++++++++++-------
 8 files changed, 134 insertions(+), 30 deletions(-)
 create mode 100644 shifts/templates/shift_edit_special.html

diff --git a/shifts/hrcodes.py b/shifts/hrcodes.py
index a6ca981..81d4fd1 100644
--- a/shifts/hrcodes.py
+++ b/shifts/hrcodes.py
@@ -46,7 +46,7 @@ red_days = [
 
 #
 # These, with the new 2024-09-13 agrement will be treaded special bank/pay
-reduced_days_and_bridge = [
+reduced_days = [
     date(2021, 1, 5),
     date(2021, 4, 1),
     date(2021, 4, 30),
@@ -66,12 +66,16 @@ reduced_days_and_bridge = [
     date(2024, 11, 1),
     # https://confluence.ess.eu/display/HR/Public+holidays+and+additional+days+off+%282025%29+Sweden
     # 3h
-    date(2024, 4, 17),
-    date(2024, 4, 30),
-    date(2024, 11, 1),
+    date(2025, 4, 17),
+    date(2025, 4, 30),
+    date(2025, 10, 31),
+]
+
+bridge_days = [
     # 8h:
-    date(2024, 5, 2),
-    date(2024, 5, 30),
+    date(2024, 12, 27),
+    date(2025, 5, 2),
+    date(2025, 5, 30),
 ]
 
 # The ones that are counted as OB4
@@ -225,9 +229,13 @@ def get_code_counts(shift: Shift, granulation=900, returnFactor=4, handoverTime=
     if verbose:
         print("Weekends OB3: ", counts)
 
-    for bd in reduced_days_and_bridge:
+    for bd in reduced_days:
+        if shift.date == bd:
+            counts["BTPorBTB"] = 3 + handoverTime
+
+    for bd in bridge_days:
         if shift.date == bd:
-            counts["BDTorBDB"] = 3 + handoverTime  # TODO or 8 check with HO
+            counts["BTPorBTB"] = 8 + handoverTime
 
     if shift.is_changed:
         counts["ELC"] = 1  # TODO check unit
diff --git a/shifts/templates/shift_edit.html b/shifts/templates/shift_edit.html
index e4db97a..80131f3 100644
--- a/shifts/templates/shift_edit.html
+++ b/shifts/templates/shift_edit.html
@@ -24,11 +24,32 @@
                     </select>
                 </div>
                 <button class="btn btn-danger"><i class="fa-solid fa-user-tie fa-1x"></i>&nbsp; Update Shift Member</button>
-
             </div>
         </form>
         {% endif %}
 
+        {% if request.user.is_staff and edit %}
+        <div class="alert alert-danger" role="alert">
+            <h3>Status for the changes entitlement.</h3>
+            <blockquote>To modify that, visit the corresponding shift exchange transaction.</blockquote>
+            <input class="form-check-input" type="checkbox" name="is_changed"
+                                       id="is_changed" {% if shift.is_changed %}checked{% endif %} disabled>
+            <label class="form-check-label" for="is_changed">
+                                    Applicable for the ListChange?
+                                </label>
+            <input class="form-check-input" type="checkbox" name="is_changed_urgent"
+                                       id="is_changed_urgent" {% if shift.is_changed_urgent %}checked{% endif %} disabled>
+            <label class="form-check-label" for="is_changed_urgent">
+                                  Applicable for higher compensation?
+                                </label>
+            <input class="form-check-input" type="checkbox" name="is_changed_offday"
+                                       id="is_changed_offday" {% if shift.is_changed_offday %}checked{% endif %} disabled>
+            <label class="form-check-label" for="is_changed_offday">
+                                  Was that originally the assignee off day?
+                                </label>
+        </div>
+        {% endif %}
+
         {% if edit %}
         <form action="{% url 'shifter:shift-edit-post' shift.id %}" method="POST" enctype="multipart/form-data" class="form-horizontal">
             {% csrf_token %}
diff --git a/shifts/templates/shift_edit_special.html b/shifts/templates/shift_edit_special.html
new file mode 100644
index 0000000..8d10ab7
--- /dev/null
+++ b/shifts/templates/shift_edit_special.html
@@ -0,0 +1,43 @@
+            <button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#updateShift{{shift.id}}">
+                {{shift}}
+              </button>
+              <div class="modal fade" id="updateShift{{shift.id}}" tabindex="-1" aria-labelledby="updateShift" aria-hidden="true">
+                <div class="modal-dialog">
+                    <div class="modal-content">
+                        <div class="modal-header">
+                            <h5 class="modal-title" id="updateShiftLabel"><i class="fa-solid fa-user-tie fa-1x"></i> Update {{shift}}</h5>
+                            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+                        </div>
+                        <div class="modal-body">
+                        <form action="{% url 'shifter:shift-edit-special-post' shift.id %}" method="POST">
+                            {% csrf_token %}
+                            <div class="form-check">
+                                <input class="form-check-input" type="checkbox" name="is_changed"
+                                       id="is_changed" {% if shift.is_changed %}checked{% endif %}>
+                                <label class="form-check-label" for="is_changed">
+                                    Applicable for the ListChange?
+                                </label>
+                            </div>
+                            <div class="form-check">
+                                <input class="form-check-input" type="checkbox" name="is_changed_urgent"
+                                       id="is_changed_urgent" {% if shift.is_changed_urgent %}checked{% endif %}>
+                                <label class="form-check-label" for="is_changed_urgent">
+                                  Applicable for higher compensation?
+                                </label>
+                            </div>
+                            <div class="form-check">
+                                <input class="form-check-input" type="checkbox" name="is_changed_offday"
+                                       id="is_changed_offday" {% if shift.is_changed_offday %}checked{% endif %}>
+                                <label class="form-check-label" for="is_changed_offday">
+                                  Was that originally the assignee off day?
+                                </label>
+                            </div>
+                            <div class="modal-footer">
+                                <button type="button" class="btn btn-light" data-bs-dismiss="modal">Close</button>
+                                <button type="submit" class="btn btn-warning"><i class="fa-solid fa-user-tie fa-1x"></i> Submit changes</button>
+                            </div>
+                        </form>
+                        </div>
+                    </div>
+                </div>
+            </div>
diff --git a/shifts/templates/shiftexchange_edit.html b/shifts/templates/shiftexchange_edit.html
index fec44a4..2288845 100644
--- a/shifts/templates/shiftexchange_edit.html
+++ b/shifts/templates/shiftexchange_edit.html
@@ -2,7 +2,7 @@
 {% block body %}
 
 {%if request.user.is_staff %}
-<div class="container mb-5 pb-5">
+<div class="container mb-4 pb-4">
     <div class="col">
         <div class="empty-div"></div>
         <div class="card text-center">
@@ -17,11 +17,16 @@
             {% for onePair in sEx.shifts.all%}
               <p> <small>[WAS]</small>
                   <a class="btn btn-outline-secondary" href="{% url 'shifter:shift-view' onePair.shift.id %}">{{onePair.shift}}</a> <i class="fa-solid fa-arrows-left-right"></i>
-                  <small>[IS now]</small> {{onePair.shift_for_exchange.member}}</p>
-            <p>[WAS] <a class="btn btn-outline-secondary" href="{% url 'shifter:shift-view' onePair.shift_for_exchange.id %}">{{onePair.shift_for_exchange}}</a>
+                  <small>[IS now]</small>
+              {% include 'shift_edit_special.html' with shift=newShiftRequestor %}
+              </p>
+              {% if newShiftRequested != None %}
+              <p><small>[WAS]</small> <a class="btn btn-outline-secondary" href="{% url 'shifter:shift-view' onePair.shift_for_exchange.id %}">{{onePair.shift_for_exchange}}</a>
                 <i class="fa-solid fa-arrows-left-right"></i>
-                  <small>[IS now]</small>  {{onePair.shift.member}}
-                <a class="btn btn-outline-success" href="{% url 'shifter:shift-view' newShift.id %}">{{newShift}}</a></p>
+                  <small>[IS now]</small>
+                  {% include 'shift_edit_special.html' with shift=newShiftRequested %}
+              </p>
+              {% endif %}
             <hr>
             {%endfor%}
           </div>
diff --git a/shifts/templates/team_desiderata.html b/shifts/templates/team_desiderata.html
index 8b0bc72..bce477a 100644
--- a/shifts/templates/team_desiderata.html
+++ b/shifts/templates/team_desiderata.html
@@ -245,7 +245,7 @@
                                 <div class="form-group row">
                                     <label for="shiftswap_date_range_picker" class="col-sm-2 col-form-label">Date-range :</label>
                                     <div class="col-sm-10">
-                                        <input class="form-control"  type="text" id="shiftswap_date_range_picker" name="daterange" value="{{ default_start | date:"m/d/y" }} - {{ default_end | date:"m/d/y" }}" />
+                                        <input class="form-control"  type="text" id="shiftswap_date_range_picker" name="daterange" value="{{ year_start | date:"m/d/y" }} - {{ year_end | date:"m/d/y" }}" />
                                     </div>
                                 </div>
                             </form>
diff --git a/shifts/urls/main.py b/shifts/urls/main.py
index 527c500..63e79ec 100644
--- a/shifts/urls/main.py
+++ b/shifts/urls/main.py
@@ -25,6 +25,7 @@ urlpatterns = [
     path("shift/<int:sid>", views.shift_edit, name="shift-edit"),
     path("shift/<int:sid>/view", views.shift_view, name="shift-view"),
     path("shift/<int:sid>/edit", views.shift_edit_post, name="shift-edit-post"),
+    path("shift/<int:sid>/edit-special", views.shift_edit_special_post, name="shift-edit-special-post"),
     path("shift-market/", views.market_shifts, name="shifts_market"),
     path("shift/create", views.shift_create, name="shifter.shift_create"),
     path("shift-market/<int:shift_id>/view/", views.market_shift_view, name="market_shift_view"),
diff --git a/shifts/views/desiderata.py b/shifts/views/desiderata.py
index 2062095..3855ef9 100644
--- a/shifts/views/desiderata.py
+++ b/shifts/views/desiderata.py
@@ -31,6 +31,8 @@ def team_view(request: HttpRequest, team_id: int) -> HttpResponse:
     default_start = datetime.date(year, month, 1)
     endMonth = month + 1 if month < 12 else 1
     default_end = datetime.date(year, endMonth, 1) + datetime.timedelta(days=-1)
+    year_start = datetime.date(year, month, 1)
+    year_end = year_start + datetime.timedelta(days=365)
 
     the_team = get_object_or_404(Team, id=team_id)
     logged_user = request.user
@@ -44,6 +46,8 @@ def team_view(request: HttpRequest, team_id: int) -> HttpResponse:
             "team": the_team,
             "default_start": default_start,
             "default_end": default_end,
+            "year_start": year_start,
+            "year_end": year_end,
             "latest_revision": Revision.objects.filter(valid=True).order_by("-number").first(),
             "revisions": Revision.objects.order_by("-number"),
             "teamShifters": Member.objects.filter(team=the_team, is_active=True),
diff --git a/shifts/views/main.py b/shifts/views/main.py
index 7374143..c7f1fc7 100644
--- a/shifts/views/main.py
+++ b/shifts/views/main.py
@@ -273,26 +273,34 @@ def shiftExchangeRequestCancel(request, ex_id=None):
 @login_required
 def shiftExchangeView(request, ex_id=None):
     sEx = ShiftExchange.objects.get(id=ex_id)
-    print(sEx.shifts)
     r = Revision.objects.filter(valid=True).order_by("-number")[0]
-    print(r)
 
-    class Aaa:
-        id = 666
+    class PlaceHolder:
+        id = 0
 
-    s = Aaa()
-    print(s.id)
-    for one in sEx.shifts.all():
-        print(one.shift)
-        print(one.shift_for_exchange)
+        def __repr__(self):
+            return "NO SHIFT"
 
-        # s = Shift.objects.get(revision=r,
-        #                       member=one.shift.member,
-        #                       slot=one.shift_for_exchange.slot,
-        #                       date=one.shift_for_exchange.date)
-        # print(s)
+    s = PlaceHolder()
+    s2 = PlaceHolder()
 
-    context = {"sEx": sEx, "oldShiftRequestor": one.shift, "oldShiftRequested": one.shift_for_exchange, "newShift": s}
+    for one in sEx.shifts.all():
+        try:
+            s = Shift.objects.get(revision=r, member=one.shift.member, slot=one.shift_for_exchange.slot, date=one.shift_for_exchange.date)
+        except ObjectDoesNotExist:
+            pass
+        try:
+            s2 = Shift.objects.get(revision=r, member=one.shift_for_exchange.member, slot=one.shift.slot, date=one.shift.date)
+        except ObjectDoesNotExist:
+            pass
+
+    context = {
+        "sEx": sEx,
+        "oldShiftRequestor": one.shift,
+        "oldShiftRequested": one.shift_for_exchange,
+        "newShiftRequestor": s2,
+        "newShiftRequested": None if isinstance(s, PlaceHolder) else s,
+    }
     return render(request, "shiftexchange_edit.html", prepare_default_context(request, context))
 
 
@@ -737,6 +745,20 @@ def shift_edit_post(request, sid=None):
     return HttpResponseRedirect(reverse("shifter:index"))
 
 
+@require_http_methods(["POST"])
+@csrf_protect
+@login_required
+def shift_edit_special_post(request, sid=None):
+    s = Shift.objects.get(id=sid)
+    s.is_changed_urgent = "is_changed_urgent" in request.POST
+    s.is_changed = "is_changed" in request.POST
+    s.is_changed_offday = "is_changed_offday" in request.POST
+    s.save()
+    url = reverse("shifter:shift-edit", kwargs={"sid": sid})
+    messages.success(request, "<a class='href' href='{}'> Shift {} updated successfully! Edit again?</a>".format(url, s))
+    return HttpResponseRedirect(url)
+
+
 @require_http_methods(["POST"])
 @csrf_protect
 @login_required
-- 
GitLab


From 171ee5e8913a4fe5401f36d8a4e5fec15b319b34 Mon Sep 17 00:00:00 2001
From: arek <arek.gorzawski@ess.eu>
Date: Tue, 22 Oct 2024 21:23:14 +0200
Subject: [PATCH 3/8] Unified team and users table with time codes WIP #103

---
 shifts/static/js/desiderata_space.js     | 61 +++++++++++++++++-------
 shifts/static/js/desiderata_space.min.js |  2 +-
 shifts/static/js/user_space.js           | 12 ++---
 shifts/static/js/user_space.min.js       |  2 +-
 shifts/templates/team_desiderata.html    | 42 +++++++++-------
 shifts/templates/user.html               |  2 +-
 shifts/views/ajax.py                     | 37 +++++++++++---
 7 files changed, 108 insertions(+), 50 deletions(-)

diff --git a/shifts/static/js/desiderata_space.js b/shifts/static/js/desiderata_space.js
index c6e04f2..2750888 100644
--- a/shifts/static/js/desiderata_space.js
+++ b/shifts/static/js/desiderata_space.js
@@ -65,6 +65,31 @@ $(document).ready(function() {
                     show: false
                 }
             },
+            {
+                searchPanes: {
+                    show: false
+                }
+            },
+            {
+                searchPanes: {
+                    show: false
+                }
+            },
+            {
+                searchPanes: {
+                    show: false
+                }
+            },
+            {
+                searchPanes: {
+                    show: false
+                }
+            },
+            {
+                searchPanes: {
+                    show: false
+                }
+            },
         ],
         "autoWidth": false,
         footerCallback: function(row, data, start, end, display) {
@@ -76,14 +101,14 @@ $(document).ready(function() {
             };
 
             // Total over all pages
-            ob1 = api.column(2)
+            ob1 = api.column(4)
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
 
             ob1Total = api
-                .column(2, {
+                .column(4, {
                     page: 'current'
                 })
                 .data()
@@ -91,14 +116,14 @@ $(document).ready(function() {
                     return intVal(a) + intVal(b);
                 }, 0);
 
-            ob2 = api.column(3)
+            ob2 = api.column(5)
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
 
             ob2Total = api
-                .column(3, {
+                .column(5, {
                     page: 'current'
                 })
                 .data()
@@ -106,14 +131,14 @@ $(document).ready(function() {
                     return intVal(a) + intVal(b);
                 }, 0);
 
-            ob3 = api.column(4)
+            ob3 = api.column(6)
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
 
             ob3Total = api
-                .column(4, {
+                .column(6, {
                     page: 'current'
                 })
                 .data()
@@ -122,14 +147,14 @@ $(document).ready(function() {
                 }, 0);
 
 
-            ob4 = api.column(5)
+            ob4 = api.column(7)
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
 
             ob4Total = api
-                .column(5, {
+                .column(7, {
                     page: 'current'
                 })
                 .data()
@@ -137,27 +162,27 @@ $(document).ready(function() {
                     return intVal(a) + intVal(b);
                 }, 0);
 
-            nwh = api.column(6)
+            nwh = api.column(3)
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
 
             nwhTotal = api
-                .column(6, {
+                .column(3, {
                     page: 'current'
                 })
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
-            vac = api.column(6)
+            vac = api.column(2)
                 .data()
                 .reduce(function(a, b) {
                     return intVal(a) + intVal(b);
                 }, 0);
 
-            vacTotal = api.column(6, {
+            vacTotal = api.column(2, {
                     page: 'current'
                 })
                 .data()
@@ -165,12 +190,12 @@ $(document).ready(function() {
                     return intVal(a) + intVal(b);
                 }, 0);
             // Update footer
-            $(api.column(2).footer()).html(ob1Total + ' hours (' + ob1 + 'h total)');
-            $(api.column(3).footer()).html(ob2Total + ' hours (' + ob2 + 'h total)');
-            $(api.column(4).footer()).html(ob3Total + ' hours (' + ob3 + 'h total)');
-            $(api.column(5).footer()).html(ob4Total + ' hours (' + ob4 + 'h total)');
-            $(api.column(6).footer()).html(nwhTotal + ' hours (' + nwh + 'h total)');
-            $(api.column(6).footer()).html(vacTotal + ' days (' + vac + 'd total)');
+            $(api.column(2).footer()).html(vacTotal.toFixed(2) + 'd (' + vac.toFixed(2) + 'd)');
+            $(api.column(3).footer()).html(nwhTotal.toFixed(2) + 'h (' + nwh.toFixed(2) + 'h)');
+            $(api.column(4).footer()).html(ob1Total.toFixed(2) + 'h (' + ob1.toFixed(2) + 'h)');
+            $(api.column(5).footer()).html(ob2Total.toFixed(2) + 'h (' + ob2.toFixed(2) + 'h)');
+            $(api.column(6).footer()).html(ob3Total.toFixed(2) + 'h (' + ob3.toFixed(2) + 'h)');
+            $(api.column(7).footer()).html(ob4Total.toFixed(2) + 'h (' + ob4.toFixed(2) + 'h)');
         },
 
 
diff --git a/shifts/static/js/desiderata_space.min.js b/shifts/static/js/desiderata_space.min.js
index 7a2006d..f364a9c 100644
--- a/shifts/static/js/desiderata_space.min.js
+++ b/shifts/static/js/desiderata_space.min.js
@@ -1 +1 @@
-$(document).ready(function(){$("#date_range_picker").daterangepicker({opens:"left",locale:{firstDay:1}}),$("#date_range_picker").on("change",function(){$("#table_HR").DataTable().ajax.reload()}),$("#table_HR").DataTable({ajax:{url:$("#table_HR").data("source"),data:function(e){e.start=$("#date_range_picker").data("daterangepicker").startDate.format("YYYY-MM-DD"),e.end=$("#date_range_picker").data("daterangepicker").endDate.format("YYYY-MM-DD"),e.team=$("team_id").data("id")}},dom:"P",bPaginate:!1,paging:!1,searchPanes:{initCollapsed:!0},columns:[{searchPanes:{show:!1}},{visible:!0,searchPanes:{show:!0}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},],autoWidth:!1,footerCallback:function(e,a,t,n,r){var o=this.api(),u=function(e){return"string"==typeof e?1*e.replace(/[\$,]/g,""):"number"==typeof e?e:0};ob1=o.column(2).data().reduce(function(e,a){return u(e)+u(a)},0),ob1Total=o.column(2,{page:"current"}).data().reduce(function(e,a){return u(e)+u(a)},0),ob2=o.column(3).data().reduce(function(e,a){return u(e)+u(a)},0),ob2Total=o.column(3,{page:"current"}).data().reduce(function(e,a){return u(e)+u(a)},0),ob3=o.column(4).data().reduce(function(e,a){return u(e)+u(a)},0),ob3Total=o.column(4,{page:"current"}).data().reduce(function(e,a){return u(e)+u(a)},0),ob4=o.column(5).data().reduce(function(e,a){return u(e)+u(a)},0),ob4Total=o.column(5,{page:"current"}).data().reduce(function(e,a){return u(e)+u(a)},0),nwh=o.column(6).data().reduce(function(e,a){return u(e)+u(a)},0),nwhTotal=o.column(6,{page:"current"}).data().reduce(function(e,a){return u(e)+u(a)},0),vac=o.column(6).data().reduce(function(e,a){return u(e)+u(a)},0),vacTotal=o.column(6,{page:"current"}).data().reduce(function(e,a){return u(e)+u(a)},0),$(o.column(2).footer()).html(ob1Total+" hours ("+ob1+"h total)"),$(o.column(3).footer()).html(ob2Total+" hours ("+ob2+"h total)"),$(o.column(4).footer()).html(ob3Total+" hours ("+ob3+"h total)"),$(o.column(5).footer()).html(ob4Total+" hours ("+ob4+"h total)"),$(o.column(6).footer()).html(nwhTotal+" hours ("+nwh+"h total)"),$(o.column(6).footer()).html(vacTotal+" days ("+vac+"d total)")}})});
+$(document).ready(function(){$("#date_range_picker").daterangepicker({opens:"left",locale:{firstDay:1}}),$("#date_range_picker").on("change",function(){$("#table_HR").DataTable().ajax.reload()}),$("#table_HR").DataTable({ajax:{url:$("#table_HR").data("source"),data:function(e){e.start=$("#date_range_picker").data("daterangepicker").startDate.format("YYYY-MM-DD"),e.end=$("#date_range_picker").data("daterangepicker").endDate.format("YYYY-MM-DD"),e.team=$("team_id").data("id")}},dom:"P",bPaginate:!1,paging:!1,searchPanes:{initCollapsed:!0},columns:[{searchPanes:{show:!1}},{visible:!0,searchPanes:{show:!0}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},{searchPanes:{show:!1}},],autoWidth:!1,footerCallback:function(e,a,t,n,r){var o=this.api(),c=function(e){return"string"==typeof e?1*e.replace(/[\$,]/g,""):"number"==typeof e?e:0};ob1=o.column(4).data().reduce(function(e,a){return c(e)+c(a)},0),ob1Total=o.column(4,{page:"current"}).data().reduce(function(e,a){return c(e)+c(a)},0),ob2=o.column(5).data().reduce(function(e,a){return c(e)+c(a)},0),ob2Total=o.column(5,{page:"current"}).data().reduce(function(e,a){return c(e)+c(a)},0),ob3=o.column(6).data().reduce(function(e,a){return c(e)+c(a)},0),ob3Total=o.column(6,{page:"current"}).data().reduce(function(e,a){return c(e)+c(a)},0),ob4=o.column(7).data().reduce(function(e,a){return c(e)+c(a)},0),ob4Total=o.column(7,{page:"current"}).data().reduce(function(e,a){return c(e)+c(a)},0),nwh=o.column(3).data().reduce(function(e,a){return c(e)+c(a)},0),nwhTotal=o.column(3,{page:"current"}).data().reduce(function(e,a){return c(e)+c(a)},0),vac=o.column(2).data().reduce(function(e,a){return c(e)+c(a)},0),vacTotal=o.column(2,{page:"current"}).data().reduce(function(e,a){return c(e)+c(a)},0),$(o.column(2).footer()).html(vacTotal.toFixed(2)+"d ("+vac.toFixed(2)+"d)"),$(o.column(3).footer()).html(nwhTotal.toFixed(2)+"h ("+nwh.toFixed(2)+"h)"),$(o.column(4).footer()).html(ob1Total.toFixed(2)+"h ("+ob1.toFixed(2)+"h)"),$(o.column(5).footer()).html(ob2Total.toFixed(2)+"h ("+ob2.toFixed(2)+"h)"),$(o.column(6).footer()).html(ob3Total.toFixed(2)+"h ("+ob3.toFixed(2)+"h)"),$(o.column(7).footer()).html(ob4Total.toFixed(2)+"h ("+ob4.toFixed(2)+"h)")}})});
diff --git a/shifts/static/js/user_space.js b/shifts/static/js/user_space.js
index c85c962..bb3b251 100644
--- a/shifts/static/js/user_space.js
+++ b/shifts/static/js/user_space.js
@@ -143,12 +143,12 @@ $(document).ready(function() {
                 }, 0);
 
             // Update footer
-            $(api.column(1).footer()).html(ob1Total + 'd (' + vac + 'd)');
-            $(api.column(2).footer()).html(ob2Total + 'h (' + nwh + 'h)');
-            $(api.column(3  ).footer()).html(ob3Total + 'h (' + ob1 + 'h)');
-            $(api.column(4).footer()).html(ob4Total + 'h (' + ob2 + 'h)');
-            $(api.column(5).footer()).html(nwhTotal + 'h (' + ob3 + 'h)');
-            $(api.column(6).footer()).html(vacTotal + 'h (' + ob4 + 'h)');
+            $(api.column(1).footer()).html(vacTotal.toFixed(2) + 'd (' + vac.toFixed(2) + 'd)');
+            $(api.column(2).footer()).html(nwhTotal.toFixed(2) + 'h (' + nwh.toFixed(2) + 'h)');
+            $(api.column(3).footer()).html(ob1Total.toFixed(2) + 'h (' + ob1.toFixed(2) + 'h)');
+            $(api.column(4).footer()).html(ob2Total.toFixed(2) + 'h (' + ob2.toFixed(2) + 'h)');
+            $(api.column(5).footer()).html(ob3Total.toFixed(2) + 'h (' + ob3.toFixed(2) + 'h)');
+            $(api.column(6).footer()).html(ob4Total.toFixed(2) + 'h (' + ob4.toFixed(2) + 'h)');
             var total = (ob1/600 + ob2/450 + ob3/300 + ob4/150) * 100;
             $("#helpOB").html(total.toFixed(1)+'%');
         },
diff --git a/shifts/static/js/user_space.min.js b/shifts/static/js/user_space.min.js
index 970ebf3..80bd53f 100644
--- a/shifts/static/js/user_space.min.js
+++ b/shifts/static/js/user_space.min.js
@@ -1 +1 @@
-getUrlParameter=function t(e){var a,n,r=window.location.search.substring(1).split("&");for(n=0;n<r.length;n++)if((a=r[n].split("="))[0]===e)return void 0===a[1]||decodeURIComponent(a[1]);return!1},$(document).ready(function(){$("#date_range_picker").daterangepicker({opens:"left",locale:{firstDay:1}}),$("#date_range_picker").on("change",function(){$("#table_HR").DataTable().ajax.reload()}),$("#table_HR").DataTable({ajax:{url:$("#table_HR").data("source"),data:function(t){t.start=$("#date_range_picker").data("daterangepicker").startDate.format("YYYY-MM-DD"),t.end=$("#date_range_picker").data("daterangepicker").endDate.format("YYYY-MM-DD")}},autoWidth:!1,buttons:[{extend:"collection",text:"Export",buttons:["copy","csv",]}],dom:"Bfrtip",pageLength:25,footerCallback:function(t,e,a,n,r){var o=this.api(),c=function(t){return"string"==typeof t?1*t.replace(/[\$,]/g,""):"number"==typeof t?t:0};ob1=o.column(1).data().reduce(function(t,e){return c(t)+c(e)},0),ob1Total=o.column(1,{page:"current"}).data().reduce(function(t,e){return c(t)+c(e)},0),ob2=o.column(2).data().reduce(function(t,e){return c(t)+c(e)},0),ob2Total=o.column(2,{page:"current"}).data().reduce(function(t,e){return c(t)+c(e)},0),ob3=o.column(3).data().reduce(function(t,e){return c(t)+c(e)},0),ob3Total=o.column(3,{page:"current"}).data().reduce(function(t,e){return c(t)+c(e)},0),ob4=o.column(4).data().reduce(function(t,e){return c(t)+c(e)},0),ob4Total=o.column(4,{page:"current"}).data().reduce(function(t,e){return c(t)+c(e)},0),nwh=o.column(5).data().reduce(function(t,e){return c(t)+c(e)},0),nwhTotal=o.column(5,{page:"current"}).data().reduce(function(t,e){return c(t)+c(e)},0),vac=o.column(6).data().reduce(function(t,e){return c(t)+c(e)},0),vacTotal=o.column(6,{page:"current"}).data().reduce(function(t,e){return c(t)+c(e)},0),$(o.column(1).footer()).html(ob1Total+" hours ("+ob1+"h total)"),$(o.column(2).footer()).html(ob2Total+" hours ("+ob2+"h total)"),$(o.column(3).footer()).html(ob3Total+" hours ("+ob3+"h total)"),$(o.column(4).footer()).html(ob4Total+" hours ("+ob4+"h total)"),$(o.column(5).footer()).html(nwhTotal+" hours ("+nwh+"h total)"),$(o.column(6).footer()).html(vacTotal+" days ("+vac+"d total)");var u=(ob1/600+ob2/450+ob3/300+ob4/150)*100;$("#helpOB").html(u.toFixed(1)+"%")}});let t=document.getElementById("live_toast");var e=getUrlParameter("ical");if(e){let a=document.querySelectorAll("#user_tabs button");a.forEach(t=>{let e=new bootstrap.Tab(t);t.addEventListener("click",t=>{t.preventDefault(),e.show()})});let n=document.querySelector('#user_tabs button[data-bs-target="#links-tab-pane"]');bootstrap.Tab.getInstance(n).show()}var e=getUrlParameter("swaps");if(e){let r=document.querySelectorAll("#user_tabs button");r.forEach(t=>{let e=new bootstrap.Tab(t);t.addEventListener("click",t=>{t.preventDefault(),e.show()})});let o=document.querySelector('#user_tabs button[data-bs-target="#shift-swap-tab-pane"]');bootstrap.Tab.getInstance(o).show()}var e=getUrlParameter("notifications");if(e){let c=document.querySelectorAll("#user_tabs button");c.forEach(t=>{let e=new bootstrap.Tab(t);t.addEventListener("click",t=>{t.preventDefault(),e.show()})});let u=document.querySelector('#user_tabs button[data-bs-target="#notify-tab-pane"]');bootstrap.Tab.getInstance(u).show()}$("#personal_link_clipboard").click(function(){navigator.clipboard.writeText($("#personal_link_clipboard_data").val()).then(function(){$("#toast_content").text("Data copied to clipboard. User Ctrl+P to paste data.")},function(){$("#toast_content").text("Failure to copy. Check permissions for clipboard")});let e=new bootstrap.Toast(t);e.show()}),$("#team_link_clipboard").click(function(){navigator.clipboard.writeText($("#team_link_clipboard_data").val()).then(function(){$("#toast_content").text("Data copied to clipboard. User Ctrl+P to paste data.")},function(){$("#toast_content").text("Failure to copy. Check permissions for clipboard")});let e=new bootstrap.Toast(t);e.show()})});
+getUrlParameter=function t(e){var a,n,o=window.location.search.substring(1).split("&");for(n=0;n<o.length;n++)if((a=o[n].split("="))[0]===e)return void 0===a[1]||decodeURIComponent(a[1]);return!1},$(document).ready(function(){$("#date_range_picker").daterangepicker({opens:"left",locale:{firstDay:1}}),$("#date_range_picker").on("change",function(){$("#table_HR").DataTable().ajax.reload()}),$("#table_HR").DataTable({ajax:{url:$("#table_HR").data("source"),data:function(t){t.start=$("#date_range_picker").data("daterangepicker").startDate.format("YYYY-MM-DD"),t.end=$("#date_range_picker").data("daterangepicker").endDate.format("YYYY-MM-DD")}},autoWidth:!1,buttons:[{extend:"collection",text:"Export",buttons:["copy","csv",]}],dom:"Bfrtip",pageLength:25,footerCallback:function(t,e,a,n,o){var r=this.api(),c=function(t){return"string"==typeof t?1*t.replace(/[\$,]/g,""):"number"==typeof t?t:0};ob1=r.column(3).data().reduce(function(t,e){return c(t)+c(e)},0),ob1Total=r.column(3,{page:"current"}).data().reduce(function(t,e){return c(t)+c(e)},0),ob2=r.column(4).data().reduce(function(t,e){return c(t)+c(e)},0),ob2Total=r.column(4,{page:"current"}).data().reduce(function(t,e){return c(t)+c(e)},0),ob3=r.column(5).data().reduce(function(t,e){return c(t)+c(e)},0),ob3Total=r.column(5,{page:"current"}).data().reduce(function(t,e){return c(t)+c(e)},0),ob4=r.column(6).data().reduce(function(t,e){return c(t)+c(e)},0),ob4Total=r.column(6,{page:"current"}).data().reduce(function(t,e){return c(t)+c(e)},0),nwh=r.column(2).data().reduce(function(t,e){return c(t)+c(e)},0),nwhTotal=r.column(2,{page:"current"}).data().reduce(function(t,e){return c(t)+c(e)},0),vac=r.column(1).data().reduce(function(t,e){return c(t)+c(e)},0),vacTotal=r.column(1,{page:"current"}).data().reduce(function(t,e){return c(t)+c(e)},0),$(r.column(1).footer()).html(vacTotal.toFixed(2)+"d ("+vac.toFixed(2)+"d)"),$(r.column(2).footer()).html(nwhTotal.toFixed(2)+"h ("+nwh.toFixed(2)+"h)"),$(r.column(3).footer()).html(ob1Total.toFixed(2)+"h ("+ob1.toFixed(2)+"h)"),$(r.column(4).footer()).html(ob2Total.toFixed(2)+"h ("+ob2.toFixed(2)+"h)"),$(r.column(5).footer()).html(ob3Total.toFixed(2)+"h ("+ob3.toFixed(2)+"h)"),$(r.column(6).footer()).html(ob4Total.toFixed(2)+"h ("+ob4.toFixed(2)+"h)");var u=(ob1/600+ob2/450+ob3/300+ob4/150)*100;$("#helpOB").html(u.toFixed(1)+"%")}});let t=document.getElementById("live_toast");var e=getUrlParameter("ical");if(e){let a=document.querySelectorAll("#user_tabs button");a.forEach(t=>{let e=new bootstrap.Tab(t);t.addEventListener("click",t=>{t.preventDefault(),e.show()})});let n=document.querySelector('#user_tabs button[data-bs-target="#links-tab-pane"]');bootstrap.Tab.getInstance(n).show()}var e=getUrlParameter("swaps");if(e){let o=document.querySelectorAll("#user_tabs button");o.forEach(t=>{let e=new bootstrap.Tab(t);t.addEventListener("click",t=>{t.preventDefault(),e.show()})});let r=document.querySelector('#user_tabs button[data-bs-target="#shift-swap-tab-pane"]');bootstrap.Tab.getInstance(r).show()}var e=getUrlParameter("notifications");if(e){let c=document.querySelectorAll("#user_tabs button");c.forEach(t=>{let e=new bootstrap.Tab(t);t.addEventListener("click",t=>{t.preventDefault(),e.show()})});let u=document.querySelector('#user_tabs button[data-bs-target="#notify-tab-pane"]');bootstrap.Tab.getInstance(u).show()}$("#personal_link_clipboard").click(function(){navigator.clipboard.writeText($("#personal_link_clipboard_data").val()).then(function(){$("#toast_content").text("Data copied to clipboard. User Ctrl+P to paste data.")},function(){$("#toast_content").text("Failure to copy. Check permissions for clipboard")});let e=new bootstrap.Toast(t);e.show()}),$("#team_link_clipboard").click(function(){navigator.clipboard.writeText($("#team_link_clipboard_data").val()).then(function(){$("#toast_content").text("Data copied to clipboard. User Ctrl+P to paste data.")},function(){$("#toast_content").text("Failure to copy. Check permissions for clipboard")});let e=new bootstrap.Toast(t);e.show()})});
diff --git a/shifts/templates/team_desiderata.html b/shifts/templates/team_desiderata.html
index bce477a..93e7ee1 100644
--- a/shifts/templates/team_desiderata.html
+++ b/shifts/templates/team_desiderata.html
@@ -112,14 +112,19 @@
                                         <table class="table table-striped" id="table_HR" data-source="{% url 'ajax.get_team_hr_codes' %}">
                                             <thead>
                                                 <tr>
-                                                    <th scope="col">#</th>
-                                                    <th scope="col">Member</th>
-                                                    <th scope="col">OB1 hours</th>
-                                                    <th scope="col">OB2 hours</th>
-                                                    <th scope="col">OB3 hours</th>
-                                                    <th scope="col">OB4 hours</th>
-                                                    <th scope="col">NWH hours</th>
-                                                    <th scope="col">Vacation</th>
+                                                <th scope="col">#</th>
+                                                <th scope="col">Member</th>
+                                                <th scope="col">Vac [d]</th>
+                                                <th scope="col">NWH [h]</th>
+                                                <th scope="col">OB1 [h]</th>
+                                                <th scope="col">OB2 [h]</th>
+                                                <th scope="col">OB3 [h]</th>
+                                                <th scope="col">OB4 [h]</th>
+                                                <th scope="col">BD [h]</th>
+                                                <th scope="col">ELC [1]</th>
+                                                <th scope="col">LC1 [1]</th>
+                                                <th scope="col">LC2 [1]</th>
+                                                <th scope="col">OT [h]</th>
                                                 </tr>
                                             </thead>
                                             <tbody>
@@ -127,14 +132,19 @@
                                             </tbody>
                                             <tfoot>
                                                 <tr>
-                                                    <th></th>
-                                                    <th></th>
-                                                    <th></th>
-                                                    <th></th>
-                                                    <th></th>
-                                                    <th></th>
-                                                    <th></th>
-                                                    <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
+                                                <th></th>
                                                 </tr>
                                             </tfoot>
                                         </table>
diff --git a/shifts/templates/user.html b/shifts/templates/user.html
index 55618ea..268be4d 100644
--- a/shifts/templates/user.html
+++ b/shifts/templates/user.html
@@ -449,7 +449,7 @@
 
 {%  block js %}
 <script type="text/javascript" src="{% static 'js/calendar_script.min.js' %}"></script>
-<script type="text/javascript" src="{% static 'js/user_space.js' %}"></script>
+<script type="text/javascript" src="{% static 'js/user_space.min.js' %}"></script>
 <script type="text/javascript" src="{% static 'datatables.net/js/jquery.dataTables.min.js' %}"></script>
 <script type="text/javascript" src="{% static 'datatables.net-select/js/dataTables.select.min.js' %}"></script>
 <script type="text/javascript" src="{% static 'datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
diff --git a/shifts/views/ajax.py b/shifts/views/ajax.py
index 411eaee..228195c 100644
--- a/shifts/views/ajax.py
+++ b/shifts/views/ajax.py
@@ -343,17 +343,40 @@ def get_team_hr_codes(request: HttpRequest) -> HttpResponse:
 
         shift2codes = get_date_code_counts(scheduled_shifts)
 
-        def _check(date):
-            if date in dates:
-                return "1"
-            return
-
         for date, item in shift2codes.items():
-            a_row = [date, str(m), _trim4vis(item["OB1"]), _trim4vis(item["OB2"]), _trim4vis(item["OB3"]), _trim4vis(item["OB4"]), _trim4vis(item["NWH"]), ""]
+            a_row = [
+                date[2:],
+                str(m),
+                "",  # vacation comes in the other line
+                _trim4vis(item["NWH"]),
+                _trim4vis(item["OB1"]),
+                _trim4vis(item["OB2"]),
+                _trim4vis(item["OB3"]),
+                _trim4vis(item["OB4"]),
+                _trim4vis(item["BTPorBTB"]),
+                _trim4vis(item["ELC"]),
+                _trim4vis(item["LC1"]),
+                _trim4vis(item["LC2"]),
+                _trim4vis(item["OT"]),
+            ]
             data.append(a_row)
 
         for date in dates:
-            a_row = [date, str(m), "", "", "", "", "", "1"]
+            a_row = [
+                date[2:],
+                str(m),
+                "1",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+            ]
             data.append(a_row)
 
     return HttpResponse(json.dumps({"data": data}), content_type="application/json")
-- 
GitLab


From 2c42649b4ac86cd90e8a4f7929f29256870b7e6a Mon Sep 17 00:00:00 2001
From: arek <arek.gorzawski@ess.eu>
Date: Mon, 4 Nov 2024 12:38:28 +0100
Subject: [PATCH 4/8] Moving 31/Dec from regular red to OB4

---
 shifts/hrcodes.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shifts/hrcodes.py b/shifts/hrcodes.py
index 81d4fd1..5e63b22 100644
--- a/shifts/hrcodes.py
+++ b/shifts/hrcodes.py
@@ -41,7 +41,6 @@ red_days = [
     date(2025, 5, 1),
     date(2025, 5, 29),
     date(2025, 6, 6),
-    date(2025, 12, 31),
 ]
 
 #
@@ -129,6 +128,7 @@ public_holidays_special = [
     date(2025, 12, 24),
     date(2025, 12, 25),
     date(2025, 12, 26),
+    date(2025, 12, 31),
 ]
 
 
-- 
GitLab


From 911d7840f3f2b18f89f6b6627f00c99a36f93063 Mon Sep 17 00:00:00 2001
From: arek <arek.gorzawski@ess.eu>
Date: Mon, 4 Nov 2024 13:14:32 +0100
Subject: [PATCH 5/8] Another 31/Dec updated status (OB3 to OB4)

---
 shifts/hrcodes.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shifts/hrcodes.py b/shifts/hrcodes.py
index 5e63b22..15e1c34 100644
--- a/shifts/hrcodes.py
+++ b/shifts/hrcodes.py
@@ -35,7 +35,6 @@ red_days = [
     date(2024, 5, 10),
     date(2024, 6, 6),
     date(2024, 6, 7),
-    date(2024, 12, 31),
     # https://confluence.ess.eu/display/HR/Public+holidays+and+additional+days+off+%282025%29+Sweden
     date(2025, 1, 6),
     date(2025, 5, 1),
@@ -118,6 +117,7 @@ public_holidays_special = [
     date(2024, 12, 24),
     date(2024, 12, 25),
     date(2024, 12, 26),
+    date(2024, 12, 31),
     # https://confluence.ess.eu/display/HR/Public+holidays+and+additional+days+off+%282025%29+Sweden
     date(2025, 1, 1),
     date(2025, 4, 18),
-- 
GitLab


From dfb46f5e5f5d7633cba21a731c8dfca0c16605bc Mon Sep 17 00:00:00 2001
From: arek <arek.gorzawski@ess.eu>
Date: Mon, 4 Nov 2024 15:29:07 +0100
Subject: [PATCH 6/8] Finalised shift exchange preview and edits

---
 shifts/contexts.py                       | 13 ++++++-
 shifts/templates/shift_edit.html         | 47 ++++++++++++------------
 shifts/templates/shiftexchange_edit.html |  4 +-
 shifts/urls/main.py                      |  2 +-
 shifts/views/main.py                     | 30 +++++++++++----
 5 files changed, 61 insertions(+), 35 deletions(-)

diff --git a/shifts/contexts.py b/shifts/contexts.py
index f83098e..32f1642 100644
--- a/shifts/contexts.py
+++ b/shifts/contexts.py
@@ -13,6 +13,15 @@ from shifter.settings import (
 )
 
 
+def get_default_backup_revision():
+    #  TODO add some fancy handling if it does not exists
+    return Revision.objects.filter(name__startswith="BACKUP").first()
+
+
+def get_latest_valid_revision():
+    return Revision.objects.filter(valid=True).order_by("-number").first()
+
+
 def prepare_default_context(request, contextToAdd):
     """
     providing any of the following will override their default values:
@@ -21,7 +30,7 @@ def prepare_default_context(request, contextToAdd):
     @APP_NAME   name of the APP displayed in the upper left corner
     """
     date = datetime.datetime.now().date()
-    latest_revision = Revision.objects.filter(valid=True).order_by("-number").first()
+    latest_revision = get_latest_valid_revision()
 
     for oneShifterMessages in ShifterMessage.objects.filter(valid=True).order_by("-number"):
         messages.warning(request, oneShifterMessages.description)
@@ -45,7 +54,7 @@ def prepare_default_context(request, contextToAdd):
 def prepare_user_context(member, revisionNext=None):
     currentMonth = datetime.datetime.now()
     nextMonth = currentMonth + datetime.timedelta(30)  # banking rounding
-    revision = Revision.objects.filter(valid=True).order_by("-number").first()
+    revision = get_latest_valid_revision()
     newer_revisions = Revision.objects.filter(date_start__gt=revision.date_start).filter(ready_for_preview=True).filter(merged=False).order_by("-number")
     scheduled_shifts = Shift.objects.filter(member=member, revision=revision).order_by("-date")
     # scheduled_studies = StudyRequest.objects.filter(member=member,state__in=["B","D"]).order_by('slot_start', 'priority')
diff --git a/shifts/templates/shift_edit.html b/shifts/templates/shift_edit.html
index 39cb1b3..1fc6236 100644
--- a/shifts/templates/shift_edit.html
+++ b/shifts/templates/shift_edit.html
@@ -9,6 +9,29 @@
         <h2 class="display-5">{{ shift }}</h2>
             <p>{{shift.revision}}</p>
         </div>
+
+        {% if request.user.is_staff and edit and swap != None %}
+        <div class="alert alert-danger" role="alert">
+            <h3>Status for the changes' entitlement as part of the <br> {{swap}}</h3>
+            <blockquote><a class="link" href="{% url 'shifter:shift-exchange-view' swap.id%}">To modify that, visit the corresponding shift exchange transaction.</a></blockquote>
+            <input class="form-check-input" type="checkbox" name="is_changed"
+                                       id="is_changed" {% if shift.is_changed %}checked{% endif %} disabled>
+            <label class="form-check-label" for="is_changed">
+                                    Applicable for the ListChange?
+                                </label>
+            <input class="form-check-input" type="checkbox" name="is_changed_urgent"
+                                       id="is_changed_urgent" {% if shift.is_changed_urgent %}checked{% endif %} disabled>
+            <label class="form-check-label" for="is_changed_urgent">
+                                  Applicable for higher compensation?
+                                </label>
+            <input class="form-check-input" type="checkbox" name="is_changed_offday"
+                                       id="is_changed_offday" {% if shift.is_changed_offday %}checked{% endif %} disabled>
+            <label class="form-check-label" for="is_changed_offday">
+                                  Was that originally the assignee off day?
+                                </label>
+        </div>
+        {% endif %}
+
         {% if request.user.is_staff and edit %}
         <form action="{% url 'shifter:shift-single-exchange-post' shift.id %}" method="POST" enctype="multipart/form-data" class="form-horizontal">
             {% csrf_token %}
@@ -40,28 +63,6 @@
         </form>
         {% endif %}
 
-        {% if request.user.is_staff and edit %}
-        <div class="alert alert-danger" role="alert">
-            <h3>Status for the changes entitlement.</h3>
-            <blockquote>To modify that, visit the corresponding shift exchange transaction.</blockquote>
-            <input class="form-check-input" type="checkbox" name="is_changed"
-                                       id="is_changed" {% if shift.is_changed %}checked{% endif %} disabled>
-            <label class="form-check-label" for="is_changed">
-                                    Applicable for the ListChange?
-                                </label>
-            <input class="form-check-input" type="checkbox" name="is_changed_urgent"
-                                       id="is_changed_urgent" {% if shift.is_changed_urgent %}checked{% endif %} disabled>
-            <label class="form-check-label" for="is_changed_urgent">
-                                  Applicable for higher compensation?
-                                </label>
-            <input class="form-check-input" type="checkbox" name="is_changed_offday"
-                                       id="is_changed_offday" {% if shift.is_changed_offday %}checked{% endif %} disabled>
-            <label class="form-check-label" for="is_changed_offday">
-                                  Was that originally the assignee off day?
-                                </label>
-        </div>
-        {% endif %}
-
         {% if edit %}
         <form action="{% url 'shifter:shift-edit-post' shift.id %}" method="POST" enctype="multipart/form-data" class="form-horizontal">
             {% csrf_token %}
@@ -88,7 +89,7 @@
                     </label>
                 </div>
                 <div class="form-check mb-3">
-                    <input class="form-check-input" type="checkbox" name="cancelledLastMinute" id="cancelledLastMinute" {% if shift.is_cancelled %}checked{% endif %}>
+                    <input class="form-check-input" type="checkbox" name="cancelledLastMinute" id="cancelledLastMinute">
                     <label class="form-check-label" for="cancelledLastMinute">
                         Canceled last minute?
                     </label>
diff --git a/shifts/templates/shiftexchange_edit.html b/shifts/templates/shiftexchange_edit.html
index 2288845..b6ac39e 100644
--- a/shifts/templates/shiftexchange_edit.html
+++ b/shifts/templates/shiftexchange_edit.html
@@ -16,12 +16,12 @@
             <hr>
             {% for onePair in sEx.shifts.all%}
               <p> <small>[WAS]</small>
-                  <a class="btn btn-outline-secondary" href="{% url 'shifter:shift-view' onePair.shift.id %}">{{onePair.shift}}</a> <i class="fa-solid fa-arrows-left-right"></i>
+                  <a class="btn btn-outline-secondary" href="{% url 'shifter:shift-edit' onePair.shift.id %}">{{onePair.shift}}</a> <i class="fa-solid fa-arrows-left-right"></i>
                   <small>[IS now]</small>
               {% include 'shift_edit_special.html' with shift=newShiftRequestor %}
               </p>
               {% if newShiftRequested != None %}
-              <p><small>[WAS]</small> <a class="btn btn-outline-secondary" href="{% url 'shifter:shift-view' onePair.shift_for_exchange.id %}">{{onePair.shift_for_exchange}}</a>
+              <p><small>[WAS]</small> <a class="btn btn-outline-secondary" href="{% url 'shifter:shift-edit' onePair.shift_for_exchange.id %}">{{onePair.shift_for_exchange}}</a>
                 <i class="fa-solid fa-arrows-left-right"></i>
                   <small>[IS now]</small>
                   {% include 'shift_edit_special.html' with shift=newShiftRequested %}
diff --git a/shifts/urls/main.py b/shifts/urls/main.py
index 63e79ec..1bd29a4 100644
--- a/shifts/urls/main.py
+++ b/shifts/urls/main.py
@@ -34,7 +34,7 @@ urlpatterns = [
     path("shift/<int:shift_id>/add-to-market/", views.market_shift_add, name="add_shift_to_market"),
     path("shift/<int:sid>/exchange", views.shift_single_exchange_post, name="shift-single-exchange-post"),
     path("shift-exchange", views.shiftExchangeRequestCreateOrUpdate, name="shift-exchange-request"),
-    path("shift-exchange/<int:ex_id>", views.shiftExchangeRequestCreateOrUpdate, name="shift-exchange-request"),
+    path("shift-exchange/<int:ex_id>", views.shiftExchangeRequestCreateOrUpdate, name="shift-exchange-edit"),
     path("shift-exchange/<int:ex_id>/view", views.shiftExchangeView, name="shift-exchange-view"),
     path("shift-exchange/<int:ex_id>/close", views.shiftExchangeRequestClose, name="shift-exchange-close"),
     path("shift-exchange/<int:ex_id>/cancel", views.shiftExchangeRequestCancel, name="shift-exchange-cancel"),
diff --git a/shifts/views/main.py b/shifts/views/main.py
index 167ec08..f9a1fa0 100644
--- a/shifts/views/main.py
+++ b/shifts/views/main.py
@@ -20,7 +20,7 @@ from assets.forms import AssetBookingForm, AssetBookingFormClosing
 import datetime
 import phonenumbers
 from shifts.activeshift import prepare_active_crew, prepare_for_JSON
-from shifts.contexts import prepare_default_context, prepare_user_context
+from shifts.contexts import prepare_default_context, prepare_user_context, get_default_backup_revision
 from shifter.settings import DEFAULT_SHIFT_SLOT
 from shifts.workinghours import find_working_hours
 from shifts.exchanges import (
@@ -293,7 +293,7 @@ def shiftExchangeView(request, ex_id=None):
             s2 = Shift.objects.get(revision=r, member=one.shift_for_exchange.member, slot=one.shift.slot, date=one.shift.date)
         except ObjectDoesNotExist:
             pass
-
+    #  TODO Fix the display issue for MORE than one shift in the swap
     context = {
         "sEx": sEx,
         "oldShiftRequestor": one.shift,
@@ -322,7 +322,8 @@ def shiftExchangeRequestCreateOrUpdate(request, ex_id=None):
         sEx = ShiftExchange()
         sEx.requestor = request.user
         sEx.approver = otherShift.member
-        sEx.backupRevision = Revision.objects.filter(number=1).first()
+        sEx.backupRevision = get_default_backup_revision()
+        # sEx.backupRevision = Revision.objects.filter(number=1).first()
         sEx.requested = timezone.now()
         sEx.save()
         sPair.save()
@@ -699,9 +700,26 @@ def shifts_update_status_post(request):
 def shift_edit(request, sid=None):
     s = Shift.objects.get(id=sid)
     sfm = ShiftForMarket.objects.filter(shift=s).first()
+    swap = None
+
+    sExPairAsIs = ShiftExchangePair.objects.filter(Q(shift=s) | Q(shift_for_exchange=s))
+    ss = Shift.objects.filter(revision=get_default_backup_revision(), slot=s.slot, date=s.date)
+    sExPairExplicitBackup = ShiftExchangePair.objects.filter(Q(shift__in=ss) | Q(shift_for_exchange__in=ss))
+    # print("old exPairs AS IS : ", sExPairAsIs)
+    # print("old exPairs BACKUP: ", sExPairExplicitBackup)
+    if len(sExPairAsIs) or len(sExPairExplicitBackup):
+        try:
+            swap = ShiftExchange.objects.get(shifts__in=sExPairAsIs)
+            print(swap)
+        except:
+            swap = ShiftExchange.objects.get(shifts__in=sExPairExplicitBackup)
+            print(swap)
+    # TODO in some complicated swaps the above may trigger 'returned 2'
+
     data = {
         "shift": s,
         "on_market": sfm,
+        "swap": swap,
         "shiftRoles": ShiftRole.objects.all(),
         "replacement": Member.objects.filter(team=request.user.team, is_active=True).order_by("role"),
         "edit": True,
@@ -875,7 +893,7 @@ def market_shift_take(request, shift_id):
         request.user,
         requestor=shift_market.offerer,
         approver=request.user,
-        revisionBackup=Revision.objects.filter(name__startswith="BACKUP").first(),
+        revisionBackup=get_default_backup_revision(),
         exType="Market",
     )
     notificationService.notify(sExDone)
@@ -930,9 +948,7 @@ def shift_single_exchange_post(request, sid=None):
             return HttpResponseRedirect(reverse("shifter:index"))
 
     s.save()
-    sExDone = perform_simplified_exchange_and_save_backup(
-        s, m, requestor=request.user, approver=request.user, revisionBackup=Revision.objects.filter(name__startswith="BACKUP").first()
-    )
+    sExDone = perform_simplified_exchange_and_save_backup(s, m, requestor=request.user, approver=request.user, revisionBackup=get_default_backup_revision())
     messages.success(request, "Requested shift exchange (update) is now successfully implemented.")
     notificationService.notify(sExDone)
 
-- 
GitLab


From 70976674a0cf1aba8069b63ec115a4c8fe8f4322 Mon Sep 17 00:00:00 2001
From: arek <arek.gorzawski@ess.eu>
Date: Thu, 7 Nov 2024 11:17:47 +0100
Subject: [PATCH 7/8] Exposed Additional shift changes for rotamaker Shift
 edit/shift exchange

---
 shifts/templates/shift_edit.html         | 10 +++++++---
 shifts/templates/shift_edit_special.html | 10 ++++++----
 shifts/templates/shiftexchange_edit.html |  4 ++--
 3 files changed, 15 insertions(+), 9 deletions(-)

diff --git a/shifts/templates/shift_edit.html b/shifts/templates/shift_edit.html
index 1fc6236..f1f4777 100644
--- a/shifts/templates/shift_edit.html
+++ b/shifts/templates/shift_edit.html
@@ -13,7 +13,7 @@
         {% if request.user.is_staff and edit and swap != None %}
         <div class="alert alert-danger" role="alert">
             <h3>Status for the changes' entitlement as part of the <br> {{swap}}</h3>
-            <blockquote><a class="link" href="{% url 'shifter:shift-exchange-view' swap.id%}">To modify that, visit the corresponding shift exchange transaction.</a></blockquote>
+            <blockquote><a class="link" href="{% url 'shifter:shift-exchange-view' swap.id%}">Visit the corresponding shift exchange transaction.</a></blockquote>
             <input class="form-check-input" type="checkbox" name="is_changed"
                                        id="is_changed" {% if shift.is_changed %}checked{% endif %} disabled>
             <label class="form-check-label" for="is_changed">
@@ -37,9 +37,9 @@
             {% csrf_token %}
             <div class="alert alert-danger" role="alert">
                 <h4 class="alert-heading">Re-assign Shift Member</h4>
-                <p>Note: Updating the member will create an <strong>approved ShiftExchange</strong> with respective notifications!</p>
+
                 <div class="mb-3">
-                    <label for="shiftMember" class="form-label">Select New Shift Member:</label>
+                    <label for="shiftMember" class="form-label">Select New Shift Member: <p>Note: Updating the member will create an <strong>approved ShiftExchange</strong> with respective notifications!</p></label>
                     <select class="form-select" name="shiftMember" id="shiftMember">
                         {% for sr in replacement %}
                         <option value="{{ sr.id }}" {% if shift.member == sr %} selected {% endif %}>Shifter - {{ sr.name }}</option>
@@ -59,8 +59,12 @@
                     </select>
                 </div>
                 <button class="btn btn-danger"><i class="fa-solid fa-user-tie fa-1x"></i>&nbsp; Update Shift Member</button>
+                <br><br>
+                <h4>Apply/modify special conditions </h4>
+                {% include 'shift_edit_special.html' with shift=shift style="danger"%}
             </div>
         </form>
+
         {% endif %}
 
         {% if edit %}
diff --git a/shifts/templates/shift_edit_special.html b/shifts/templates/shift_edit_special.html
index 8d10ab7..9d51c6e 100644
--- a/shifts/templates/shift_edit_special.html
+++ b/shifts/templates/shift_edit_special.html
@@ -1,5 +1,5 @@
-            <button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#updateShift{{shift.id}}">
-                {{shift}}
+            <button type="button" class="btn btn-{{style}}" data-bs-toggle="modal" data-bs-target="#updateShift{{shift.id}}">
+                <i class="fa-solid fa-user-tie fa-1x"></i>&nbsp;<i class="fa-solid fa-wrench"></i> {{shift}}
               </button>
               <div class="modal fade" id="updateShift{{shift.id}}" tabindex="-1" aria-labelledby="updateShift" aria-hidden="true">
                 <div class="modal-dialog">
@@ -22,19 +22,21 @@
                                 <input class="form-check-input" type="checkbox" name="is_changed_urgent"
                                        id="is_changed_urgent" {% if shift.is_changed_urgent %}checked{% endif %}>
                                 <label class="form-check-label" for="is_changed_urgent">
-                                  Applicable for higher compensation?
+                                  Applicable for higher compensation, i.e. very last minute change?
                                 </label>
                             </div>
                             <div class="form-check">
                                 <input class="form-check-input" type="checkbox" name="is_changed_offday"
                                        id="is_changed_offday" {% if shift.is_changed_offday %}checked{% endif %}>
                                 <label class="form-check-label" for="is_changed_offday">
-                                  Was that originally the assignee off day?
+                                  Was that originally the assignee free day, i.e. is shifter called to work from non-assigned day?
                                 </label>
                             </div>
                             <div class="modal-footer">
                                 <button type="button" class="btn btn-light" data-bs-dismiss="modal">Close</button>
+                                <a class="btn btn-light" href="{% url 'shifter:shift-edit' shift.id %}"><i class="fa-solid fa-user-tie fa-1x"></i> Go to shift edit</a>
                                 <button type="submit" class="btn btn-warning"><i class="fa-solid fa-user-tie fa-1x"></i> Submit changes</button>
+
                             </div>
                         </form>
                         </div>
diff --git a/shifts/templates/shiftexchange_edit.html b/shifts/templates/shiftexchange_edit.html
index b6ac39e..e68a7e2 100644
--- a/shifts/templates/shiftexchange_edit.html
+++ b/shifts/templates/shiftexchange_edit.html
@@ -18,13 +18,13 @@
               <p> <small>[WAS]</small>
                   <a class="btn btn-outline-secondary" href="{% url 'shifter:shift-edit' onePair.shift.id %}">{{onePair.shift}}</a> <i class="fa-solid fa-arrows-left-right"></i>
                   <small>[IS now]</small>
-              {% include 'shift_edit_special.html' with shift=newShiftRequestor %}
+              {% include 'shift_edit_special.html' with shift=newShiftRequestor style="success" %}
               </p>
               {% if newShiftRequested != None %}
               <p><small>[WAS]</small> <a class="btn btn-outline-secondary" href="{% url 'shifter:shift-edit' onePair.shift_for_exchange.id %}">{{onePair.shift_for_exchange}}</a>
                 <i class="fa-solid fa-arrows-left-right"></i>
                   <small>[IS now]</small>
-                  {% include 'shift_edit_special.html' with shift=newShiftRequested %}
+                  {% include 'shift_edit_special.html' with shift=newShiftRequested style="success" %}
               </p>
               {% endif %}
             <hr>
-- 
GitLab


From 9b24ef20c649d078c6a69c3b86fa405265a57d4c Mon Sep 17 00:00:00 2001
From: arek <arek.gorzawski@ess.eu>
Date: Thu, 7 Nov 2024 12:57:21 +0100
Subject: [PATCH 8/8] Fix form submit bug - removed modal button from inside
 the form tag - added default NO update when no change is provided

---
 shifter/notifications.py                 | 17 +++++------------
 shifts/templates/shift_edit.html         |  9 ++++-----
 shifts/templates/shift_edit_special.html |  1 -
 shifts/views/main.py                     | 18 +++++++++++++++---
 4 files changed, 24 insertions(+), 21 deletions(-)

diff --git a/shifter/notifications.py b/shifter/notifications.py
index c0fd84c..0859cb8 100644
--- a/shifter/notifications.py
+++ b/shifter/notifications.py
@@ -132,7 +132,7 @@ class EmailNotifier(NotificationService):
             "return_code": NOTIFICATIONS_CODE_SHIFT,
         },
     }
-    
+
     def _notify_internal_and_external(self, actor, emailSubject, emailBody, affectedMembers, affectedMembersEmail, target=None):
         # sends the Django notifications
         notify.send(actor, recipient=affectedMembers, target=target, verb=emailSubject, description=emailBody, emailed=True)
@@ -180,18 +180,11 @@ class EmailNotifier(NotificationService):
             subject = config["subject"]
             email_body = render_to_string(config["template"], {"member": event.member, "shift": event})
             notify_message = config["notify_message"]
-            
-            send_mail(
-                subject,
-                email_body,
-                self.DEFAULT_NO_REPLY,
-                [event.member.email],
-                fail_silently=False,
-                html_message=email_body
-            )
-            
+
+            send_mail(subject, email_body, self.DEFAULT_NO_REPLY, [event.member.email], fail_silently=False, html_message=email_body)
+
             notify.send(event, recipient=event.member, verb=notify_message)
-            
+
             return config["return_code"]
 
         if isinstance(event, ShiftExchange):
diff --git a/shifts/templates/shift_edit.html b/shifts/templates/shift_edit.html
index f1f4777..a94cf20 100644
--- a/shifts/templates/shift_edit.html
+++ b/shifts/templates/shift_edit.html
@@ -33,9 +33,9 @@
         {% endif %}
 
         {% if request.user.is_staff and edit %}
+        <div class="alert alert-danger" role="alert">
         <form action="{% url 'shifter:shift-single-exchange-post' shift.id %}" method="POST" enctype="multipart/form-data" class="form-horizontal">
             {% csrf_token %}
-            <div class="alert alert-danger" role="alert">
                 <h4 class="alert-heading">Re-assign Shift Member</h4>
 
                 <div class="mb-3">
@@ -60,11 +60,10 @@
                 </div>
                 <button class="btn btn-danger"><i class="fa-solid fa-user-tie fa-1x"></i>&nbsp; Update Shift Member</button>
                 <br><br>
-                <h4>Apply/modify special conditions </h4>
-                {% include 'shift_edit_special.html' with shift=shift style="danger"%}
-            </div>
         </form>
-
+        <h4>Apply/modify special conditions </h4>
+        {% include 'shift_edit_special.html' with shift=shift style="danger"%}
+        </div>
         {% endif %}
 
         {% if edit %}
diff --git a/shifts/templates/shift_edit_special.html b/shifts/templates/shift_edit_special.html
index 9d51c6e..fc9959b 100644
--- a/shifts/templates/shift_edit_special.html
+++ b/shifts/templates/shift_edit_special.html
@@ -36,7 +36,6 @@
                                 <button type="button" class="btn btn-light" data-bs-dismiss="modal">Close</button>
                                 <a class="btn btn-light" href="{% url 'shifter:shift-edit' shift.id %}"><i class="fa-solid fa-user-tie fa-1x"></i> Go to shift edit</a>
                                 <button type="submit" class="btn btn-warning"><i class="fa-solid fa-user-tie fa-1x"></i> Submit changes</button>
-
                             </div>
                         </form>
                         </div>
diff --git a/shifts/views/main.py b/shifts/views/main.py
index f9a1fa0..d69a9e0 100644
--- a/shifts/views/main.py
+++ b/shifts/views/main.py
@@ -767,11 +767,16 @@ def shift_edit_post(request, sid=None):
 @csrf_protect
 @login_required
 def shift_edit_special_post(request, sid=None):
+    print(request.POST)
     s = Shift.objects.get(id=sid)
     s.is_changed_urgent = "is_changed_urgent" in request.POST
     s.is_changed = "is_changed" in request.POST
     s.is_changed_offday = "is_changed_offday" in request.POST
     s.save()
+    print(s)
+    print(s.is_changed)
+    print(s.is_changed_offday)
+    print(s.is_changed_urgent)
     url = reverse("shifter:shift-edit", kwargs={"sid": sid})
     messages.success(request, "<a class='href' href='{}'> Shift {} updated successfully! Edit again?</a>".format(url, s))
     return HttpResponseRedirect(url)
@@ -928,12 +933,13 @@ def user_page(request):
 def shift_single_exchange_post(request, sid=None):
     s = Shift.objects.get(id=sid)
     m = Member.objects.get(id=int(request.POST["shiftMember"]))
+    shift_slot = s.slot
+    shift_date = s.date
 
     shift_date_str = request.POST.get("shiftDate")
     if shift_date_str:
         try:
-            shift_date = datetime.datetime.strptime(shift_date_str, "%Y-%m-%d").date()
-            s.date = shift_date
+            shift_date = datetime.datetime.strptime(shift_date_str, DATE_FORMAT).date()
         except ValueError:
             messages.error(request, "Invalid date format for shift date.")
             return HttpResponseRedirect(reverse("shifter:index"))
@@ -942,11 +948,17 @@ def shift_single_exchange_post(request, sid=None):
     if shift_slot_id:
         try:
             shift_slot = Slot.objects.get(id=int(shift_slot_id))
-            s.slot = shift_slot
         except (ValueError, Slot.DoesNotExist):
             messages.error(request, "Invalid slot selected.")
             return HttpResponseRedirect(reverse("shifter:index"))
 
+    if s.member == m and s.date == shift_date and s.slot == shift_slot:
+        messages.info(request, "No change applied, no update performed!")
+        return HttpResponseRedirect(reverse("shifter:index"))
+
+    s.member = m
+    s.slot = shift_slot
+    s.date = shift_date
     s.save()
     sExDone = perform_simplified_exchange_and_save_backup(s, m, requestor=request.user, approver=request.user, revisionBackup=get_default_backup_revision())
     messages.success(request, "Requested shift exchange (update) is now successfully implemented.")
-- 
GitLab