Skip to content

Commit 2a4144c

Browse files
committed
feat(crm): add customer follow-up reminders
Track due customer next actions in the CRM so small teams do not lose follow-ups.
1 parent 4d2254a commit 2a4144c

10 files changed

Lines changed: 603 additions & 17 deletions

File tree

weblate_web/crm/forms.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,43 @@ class ManualInteractionForm(forms.Form):
162162
)
163163

164164

165+
class CustomerFollowUpForm(forms.Form):
166+
follow_up_at = forms.DateTimeField(
167+
label=gettext_lazy("Follow-up date"),
168+
input_formats=("%Y-%m-%dT%H:%M", "%Y-%m-%d %H:%M"),
169+
widget=forms.DateTimeInput(
170+
attrs={"type": "datetime-local"},
171+
format="%Y-%m-%dT%H:%M",
172+
),
173+
)
174+
follow_up_note = forms.CharField(
175+
label=gettext_lazy("Follow-up note"),
176+
max_length=200,
177+
required=False,
178+
widget=forms.TextInput(),
179+
)
180+
181+
def __init__(self, *args, instance: Customer, **kwargs):
182+
self.instance = instance
183+
kwargs.setdefault(
184+
"initial",
185+
{
186+
"follow_up_at": instance.follow_up_at,
187+
"follow_up_note": instance.follow_up_note,
188+
},
189+
)
190+
super().__init__(*args, **kwargs)
191+
192+
def save(self, *, commit: bool = True) -> Customer:
193+
self.instance.follow_up_at = self.cleaned_data["follow_up_at"]
194+
self.instance.follow_up_note = self.cleaned_data["follow_up_note"]
195+
if commit:
196+
self.instance.save(
197+
update_fields=["follow_up_at", "follow_up_note"],
198+
)
199+
return self.instance
200+
201+
165202
class CustomerUserForm(forms.Form):
166203
email = forms.EmailField(label=gettext_lazy("E-mail"))
167204
full_name = forms.CharField(label=gettext_lazy("Full name"), max_length=150)

weblate_web/crm/static/crm.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,11 @@
365365
position: relative;
366366
}
367367

368+
.crm-dashboard-item--attention {
369+
background: var(--crm-yellow-soft);
370+
border-color: rgba(154, 103, 0, 0.2);
371+
}
372+
368373
.crm-dashboard-item:hover,
369374
.crm-dashboard-item:focus-within {
370375
border-color: var(--crm-border-strong);

weblate_web/crm/templates/crm/index.html

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,52 @@
3636
</section>
3737
{% endif %}
3838

39+
{% if perms.payments.view_customer %}
40+
<section class="crm-section crm-dashboard-section">
41+
{% translate "Follow-ups" as section_title %}
42+
{% include "crm/snippets/section_header.html" %}
43+
<div class="crm-dashboard-list">
44+
<div class="crm-dashboard-item">
45+
<a class="crm-dashboard-link"
46+
href="{% url "crm:customer-list" kind="followups" %}"
47+
aria-label="{% translate "All follow-ups" %}">
48+
<strong>{% translate "All follow-ups" %}</strong>
49+
<span>{% translate "Customers with scheduled next actions." %}</span>
50+
</a>
51+
</div>
52+
{% for customer in due_followups %}
53+
<div class="crm-dashboard-item crm-dashboard-item--attention">
54+
<a class="crm-dashboard-link" href="{{ customer.get_absolute_url }}">
55+
<strong>{{ customer.verbose_name }}</strong>
56+
<span>
57+
{% blocktranslate with date=customer.follow_up_at %}Due {{ date }}{% endblocktranslate %}
58+
{% if customer.follow_up_note %}- {{ customer.follow_up_note }}{% endif %}
59+
</span>
60+
</a>
61+
</div>
62+
{% empty %}
63+
<div class="crm-dashboard-item">
64+
<span class="crm-dashboard-link">
65+
<strong>{% translate "No due follow-ups" %}</strong>
66+
<span>{% translate "No customer follow-up is due right now." %}</span>
67+
</span>
68+
</div>
69+
{% endfor %}
70+
{% for customer in upcoming_followups %}
71+
<div class="crm-dashboard-item">
72+
<a class="crm-dashboard-link" href="{{ customer.get_absolute_url }}">
73+
<strong>{{ customer.verbose_name }}</strong>
74+
<span>
75+
{% blocktranslate with date=customer.follow_up_at %}Upcoming {{ date }}{% endblocktranslate %}
76+
{% if customer.follow_up_note %}- {{ customer.follow_up_note }}{% endif %}
77+
</span>
78+
</a>
79+
</div>
80+
{% endfor %}
81+
</div>
82+
</section>
83+
{% endif %}
84+
3985
{% if perms.invoices.view_invoice %}
4086
<section class="crm-section crm-dashboard-section">
4187
{% translate "Invoices" as section_title %}

weblate_web/crm/templates/payments/customer_detail.html

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,53 @@
118118
</dl>
119119
</section>
120120

121+
<section class="crm-section content">
122+
{% translate "Follow-up" as section_title %}
123+
{% include "crm/snippets/section_header.html" %}
124+
{% if object.follow_up_at %}
125+
<div class="crm-summary-line">
126+
<span>
127+
{% blocktranslate with date=object.follow_up_at %}Next follow-up: {{ date }}{% endblocktranslate %}
128+
</span>
129+
{% if object.follow_up_note %}<strong>{{ object.follow_up_note }}</strong>{% endif %}
130+
</div>
131+
{% else %}
132+
{% translate "No follow-up scheduled." as empty_message %}
133+
{% include "crm/snippets/empty.html" %}
134+
{% endif %}
135+
{% if can_change_customer %}
136+
<form class="crm-form-panel crm-form-panel--compact" method="post">
137+
<h3>{% translate "Set follow-up" %}</h3>
138+
{% csrf_token %}
139+
<div class="crm-form-panel__body">
140+
{{ follow_up_form.non_field_errors }}
141+
<div class="crm-field">
142+
{{ follow_up_form.follow_up_at.errors }}
143+
{{ follow_up_form.follow_up_at.label_tag }}
144+
{{ follow_up_form.follow_up_at }}
145+
</div>
146+
<div class="crm-field">
147+
{{ follow_up_form.follow_up_note.errors }}
148+
{{ follow_up_form.follow_up_note.label_tag }}
149+
{{ follow_up_form.follow_up_note }}
150+
</div>
151+
</div>
152+
<div class="crm-form-panel__actions">
153+
<input class="crm-button"
154+
type="submit"
155+
name="set_follow_up"
156+
value="{% translate "Save follow-up" %}">
157+
{% if object.follow_up_at or object.follow_up_note %}
158+
<button class="crm-button crm-button--secondary"
159+
type="submit"
160+
name="clear_follow_up"
161+
formnovalidate>{% translate "Clear follow-up" %}</button>
162+
{% endif %}
163+
</div>
164+
</form>
165+
{% endif %}
166+
</section>
167+
121168
<section class="crm-section content">
122169
{% translate "Actions" as section_title %}
123170
{% include "crm/snippets/section_header.html" %}

weblate_web/crm/templates/payments/customer_list.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
<th scope="col">{% translate "Customer" %}</th>
1818
<th scope="col">{% translate "End client" %}</th>
1919
<th scope="col">{% translate "E-mail" %}</th>
20+
{% if kind == "followups" %}
21+
<th scope="col">{% translate "Follow-up" %}</th>
22+
<th scope="col">{% translate "Next action" %}</th>
23+
{% endif %}
2024
<th scope="col">{% translate "VAT" %}</th>
2125
<th scope="col">{% translate "City" %}</th>
2226
<th scope="col">{% translate "Country" %}</th>
@@ -31,6 +35,10 @@
3135
</th>
3236
<td>{{ object.end_client|default:"-" }}</td>
3337
<td>{{ object.email|default:"-" }}</td>
38+
{% if kind == "followups" %}
39+
<td>{{ object.follow_up_at }}</td>
40+
<td>{{ object.follow_up_note|default:"-" }}</td>
41+
{% endif %}
3442
<td>{{ object.vat|default:"-" }}</td>
3543
<td>{{ object.city|default:"-" }}</td>
3644
<td>{{ object.get_country_display|default:"-" }}</td>

0 commit comments

Comments
 (0)