Skip to content

Commit

Permalink
switch to htmx for rate inline edit
Browse files Browse the repository at this point in the history
  • Loading branch information
digitalfox committed Oct 13, 2024
1 parent ecb8a14 commit aecde61
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 62 deletions.
2 changes: 1 addition & 1 deletion staffing/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
re_path(r'^non-prod_report/?$', v.missions_report, {"nature": "NONPROD"}, name="nonprod-pivotable"),
re_path(r'^non-prod_report/all$', v.missions_report, {"nature": "NONPROD", "year": "all"}, name="nonprod-pivotable-all"),
re_path(r'^contacts/mission/(?P<mission_id>\d+)/$', v.mission_contacts, name="mission_contacts"),
re_path(r'^rate/?$', v.mission_consultant_rate, name="mission_consultant_rate"),
re_path(r'^rate/mission/(?P<mission_id>\d+)/consultant/(?P<consultant_id>\d+)/$', v.mission_consultant_rate, name="mission_consultant_rate"),
re_path(r'^pdc-detail/(?P<consultant_id>\d+)/(?P<staffing_date>\d+)/?$', v.pdc_detail, name="pdc_detail"),
re_path(r'^datatable/all-missions/data/$', t.MissionsTableDT.as_view(), name='all_mission_table_DT'),
re_path(r'^datatable/consultant-all-missions/(?P<consultant_id>\d+)/data/$', t.MissionsTableDT.as_view(), name='consultant_all_mission_table_DT'),
Expand Down
14 changes: 14 additions & 0 deletions staffing/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,17 @@ def check_timesheet_validity(missions, consultant, month):
if limited_individual_mode_offending_missions:
return _("Charge cannot exceed forecast (%s)") %\
", ".join([str(m) for m in limited_individual_mode_offending_missions])


def compute_mission_consultant_rates(mission):
"""helper function to compute mission rates for each consultant for tab display and htmx edit form"""
rates = {}
objective_rates = mission.consultant_objective_rates()
for consultant, rate in mission.consultant_rates().items():
rates[consultant] = (rate, objective_rates.get(consultant))
try:
objective_dates = [i[0] for i in list(objective_rates.values())[0]]
except IndexError:
# No consultant or no objective on mission timeframe
objective_dates = []
return objective_dates, rates
67 changes: 35 additions & 32 deletions staffing/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from django.core.cache import cache
from django.shortcuts import render, redirect
from django.http import HttpResponseRedirect, HttpResponse, Http404
from django.http import HttpResponseRedirect, HttpResponse, Http404, HttpResponseForbidden
from django.contrib.auth.decorators import permission_required
from django.forms.models import inlineformset_factory
from django.forms import formset_factory
Expand Down Expand Up @@ -46,10 +46,10 @@
from core.utils import working_days, nextMonth, previousMonth, daysOfMonth, previousWeek, nextWeek, monthWeekNumber, \
to_int_or_round, COLORS, cumulateList, user_has_feature, get_parameter, \
get_fiscal_years_from_qs, get_fiscal_year
from core.decorator import pydici_non_public, pydici_feature, PydiciNonPublicdMixin, pydici_subcontractor
from core.decorator import pydici_non_public, pydici_feature, PydiciNonPublicdMixin
from staffing.utils import gatherTimesheetData, saveTimesheetData, saveFormsetAndLog, \
sortMissions, holidayDays, staffingDates, time_string_for_day_percent, \
timesheet_report_data, timesheet_report_data_grouped, check_timesheet_validity
timesheet_report_data, timesheet_report_data_grouped, check_timesheet_validity, compute_mission_consultant_rates
from staffing.forms import MissionForm, OptimiserForm, MissionOptimiserForm, MissionOptimiserFormsetHelper
from staffing.optim import solve_pdc, solver_solution_format, compute_consultant_freetime, compute_consultant_rates, solver_apply_forecast
from staffing.optim import OPTIM_NEWBIE_SENIOR_LIMIT, OPTIM_SENIOR_DIRECTOR_LIMIT
Expand Down Expand Up @@ -155,15 +155,7 @@ def mission_home(request, mission_id):
@pydici_non_public
def mission_consultants(request, mission_id):
mission = Mission.objects.get(id=mission_id)
rates = {}
objective_rates = mission.consultant_objective_rates()
for consultant, rate in mission.consultant_rates().items():
rates[consultant] = (rate, objective_rates.get(consultant))
try:
objective_dates = [i[0] for i in list(objective_rates.values())[0]]
except IndexError:
# No consultant or no objective on mission timeframe
objective_dates = []
objective_dates, rates = compute_mission_consultant_rates(mission)
return render(request, "staffing/mission_consultants.html",
{"mission": mission,
"objective_dates": objective_dates,
Expand Down Expand Up @@ -1537,7 +1529,6 @@ def holiday_csv_timesheet(request, year=None, month=None):
return response



@pydici_non_public
@pydici_feature("management")
def holidays_planning(request, year=None, month=None):
Expand Down Expand Up @@ -1732,7 +1723,6 @@ def missions_report(request, year=None, nature="HOLIDAYS"):
"derivedAttributes": [],})



@pydici_non_public
@pydici_feature("leads")
@permission_required("staffing.add_mission")
Expand Down Expand Up @@ -1771,35 +1761,48 @@ def create_new_mission_from_lead(request, lead_id):


@pydici_non_public
def mission_consultant_rate(request):
"""Select or create financial condition for this consultant/mission tuple and update it
This is intended to be used through a jquery jeditable call"""
def mission_consultant_rate(request, mission_id, consultant_id):
"""Select or create financial condition for this consultant/mission tuple and update it with htmx"""
if not (request.user.has_perm("staffing.add_financialcondition") and
request.user.has_perm("staffing.change_financialcondition")):
return HttpResponse(_("You are not allowed to do that"))
request.user.has_perm("staffing.change_financialcondition")):
return HttpResponseForbidden()

try:
sold, mission_id, consultant_id = request.POST["id"].split("-")
mission = Mission.objects.get(id=mission_id)
consultant = Consultant.objects.get(id=consultant_id)
condition, created = FinancialCondition.objects.get_or_create(mission=mission, consultant=consultant,
defaults={"daily_rate": 0})
value = escape(request.POST["value"].replace(" ", ""))
if sold == "sold":
except (Mission.DoesNotExist, Consultant.DoesNotExist):
return HttpResponse(_("Mission or consultant does not exist"), status=404)

if request.method == "GET":
edit = True
else:
edit = False
change = None
if request.POST.get("sold"):
value = request.POST["sold"]
change = {_(f"daily rate for {consultant}"): [condition.daily_rate, value]}
condition.daily_rate = value
else:

if request.POST.get("bought"):
value = request.POST["bought"]
change = {_(f"bought daily rate for {consultant}"): [condition.daily_rate, value]}
condition.bought_daily_rate = value
condition.save()
if mission.responsible:
compute_consultant_tasks.delay(mission.responsible.id)
LogEntry.objects.log_create(instance=mission, actor=request.user, action=LogEntry.Action.UPDATE, changes=json.dumps(change))

return HttpResponse(value)
except (Mission.DoesNotExist, Consultant.DoesNotExist):
return HttpResponse(_("Mission or consultant does not exist"))
except ValueError:
return HttpResponse(_("Incorrect value"))
if change:
try:
condition.save()
cache.delete("Mission.consultant_rates%s" % mission.id) # flush rate cache
except ValueError:
return HttpResponse(status=400)
if mission.responsible:
compute_consultant_tasks.delay(mission.responsible.id)
LogEntry.objects.log_create(instance=mission, actor=request.user, action=LogEntry.Action.UPDATE, changes=json.dumps(change))

objective_dates, rates = compute_mission_consultant_rates(mission)
return render(request, "staffing/_mission_consultants_rate.html", {"mission": mission, "consultant": consultant,
"rate": rates[consultant], "edit": edit})


@pydici_non_public
Expand Down
25 changes: 25 additions & 0 deletions templates/staffing/_mission_consultants_rate.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{# fragment to display and allow inline edit of consultant rate for a mission #}
{# context: consultant, rate #}
{% load l10n %}
{% load i18n %}

<tr>
<td>{% include "people/__consultant_name.html" %}</td>
{% if edit %}
<td align="right"><input id="sold" type="number" step="1" name="sold" value="{{ rate.0.0|unlocalize }}" class="text-end"
hx-post="{% url 'staffing:mission_consultant_rate' mission.id consultant.id %}" autofocus />
</td>
<td align="right">{% if consultant.subcontractor %}<input id="bought" type="number" step="1" name="bought" value="{{ rate.0.1|unlocalize }}" class="text-end"
hx-post="{% url 'staffing:mission_consultant_rate' mission.id consultant.id %}" />{% endif %}
</td>
{% else %}
<td align="right"><a href="#" hx-get="{% url 'staffing:mission_consultant_rate' mission.id consultant.id %}">{{ rate.0.0 }}</a></td>
<td align="right"><a href="#"hx-get="{% url 'staffing:mission_consultant_rate' mission.id consultant.id %}">
{% if consultant.subcontractor %}{{ rate.0.1 }}{% endif %}</a></td>
{% endif %}
{% with o_rates=rate.1 %}
{% for date, o_rate in o_rates %}
<td align="right">{{ o_rate|default_if_none:"-" }}</td>
{% endfor %}
{% endwith %}
</tr>
36 changes: 7 additions & 29 deletions templates/staffing/mission_consultants.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<h2 class="mt-2">{% trans "Consultants currently implicated in this mission" %}</h2>
<div class="col-lg-8 col-md-12 col-sm-12">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>{% trans "Consultant" %}</th>
<th style="text-align:right;">{% trans "Daily rate (€)" %}</th>
Expand All @@ -13,38 +14,15 @@ <h2 class="mt-2">{% trans "Consultants currently implicated in this mission" %}<
<th style="text-align:right;">{% blocktranslate with d=date|date:"M Y" %}Objective {{ d }} (€){% endblocktranslate %}</th>
{% endfor %}
</tr>
</thead>
<tbody hx-target="closest tr" hx-swap="outerHTML">
{% for consultant, rate in rates.items %}
<tr>
<td>{% include "people/__consultant_name.html" %}</td>
<td align="right"><div id="sold-{{ mission.id|unlocalize }}-{{ consultant.id|unlocalize }}" class="jeditable-rate" >{{ rate.0.0|unlocalize }}</div></td>
<td align="right">{% if consultant.subcontractor %}<div id="bought-{{ mission.id|unlocalize }}-{{ consultant.id }}" style="display:inline" class="jeditable-rate">{{ rate.0.1|unlocalize }}</div>{% endif %}</td>
{% with o_rates=rate.1 %}
{% for date, o_rate in o_rates %}
<td align="right">{{ o_rate|default_if_none:"-" }}</td>
{% endfor %}
{% endwith %}
</tr>
{% include "staffing/_mission_consultants_rate.html" %}
{% endfor %}
</tbody>
</table>
</div>

<script type="text/javascript">
<!--
$(document).ready(function() {
{% if mission.active %}
$(".jeditable-rate").editable("{% url 'staffing:mission_consultant_rate' %}", {
indicator : "<img src='{{ MEDIA_URL }}img/ajax-loader.gif'/>",
tooltip : "{% trans 'click to edit...' %}",
event : "click",
style : "inherit",
data: function(value, settings) {
/* remove unbreakable space */
var retval = value.replace(/&nbsp;/gi, '');
return retval;
}
});
{% endif %}

});
-->
</script>
$(document).ready(function() { htmx.process(document.body); });
</script>

0 comments on commit aecde61

Please sign in to comment.