Skip to content

Commit 5ddb029

Browse files
committed
Feat: Phone & quote visit count
1 parent 687a2eb commit 5ddb029

18 files changed

Lines changed: 126 additions & 26 deletions

quotes/admin.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
MileageEntry,
1414
Quote,
1515
QuoteItem,
16+
QuoteVisit,
1617
Service,
1718
)
1819

@@ -358,3 +359,29 @@ def is_overdue(self, obj):
358359
return obj.is_overdue
359360

360361
is_overdue.boolean = True
362+
363+
364+
@admin.register(QuoteVisit)
365+
class QuoteVisitAdmin(admin.ModelAdmin):
366+
list_display = ("visited_at", "quote_link", "ip_address", "short_user_agent")
367+
list_filter = ("visited_at",)
368+
search_fields = ("quote__invoice_number", "ip_address", "user_agent")
369+
readonly_fields = ("quote", "visited_at", "ip_address", "user_agent")
370+
ordering = ("-visited_at",)
371+
372+
def has_add_permission(self, request):
373+
return False
374+
375+
def has_change_permission(self, request, obj=None):
376+
return False
377+
378+
def quote_link(self, obj):
379+
url = reverse("admin:quotes_quote_change", args=[obj.quote_id])
380+
return format_html('<a href="{}">{}</a>', url, obj.quote)
381+
382+
quote_link.short_description = "Quote"
383+
384+
def short_user_agent(self, obj):
385+
return obj.user_agent[:80] if obj.user_agent else "-"
386+
387+
short_user_agent.short_description = "User Agent"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Generated by Django 5.1.7 on 2026-05-10 16:56
2+
3+
import django.db.models.deletion
4+
import quotes.models
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('quotes', '0011_alter_quote_expires_at_alter_quote_status'),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name='quote',
17+
name='sent_at',
18+
field=models.DateTimeField(blank=True, null=True),
19+
),
20+
migrations.AlterField(
21+
model_name='quote',
22+
name='expires_at',
23+
field=models.DateTimeField(default=quotes.models.get_default_expiration),
24+
),
25+
migrations.CreateModel(
26+
name='QuoteVisit',
27+
fields=[
28+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
29+
('visited_at', models.DateTimeField(auto_now_add=True)),
30+
('ip_address', models.GenericIPAddressField(blank=True, null=True)),
31+
('user_agent', models.TextField(blank=True)),
32+
('quote', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='visits', to='quotes.quote')),
33+
],
34+
options={
35+
'ordering': ['-visited_at'],
36+
},
37+
),
38+
]

quotes/models.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ class Quote(models.Model):
170170
access_token = models.CharField(max_length=32, unique=True, editable=False)
171171
verification_code = models.CharField(max_length=6, editable=False)
172172

173+
sent_at = models.DateTimeField(null=True, blank=True)
174+
173175
notes = models.TextField(blank=True)
174176
job_location = models.TextField(blank=True)
175177
expected_completion_date = models.DateField(null=True, blank=True)
@@ -192,6 +194,7 @@ def save(self, *args, **kwargs):
192194
old_quote = Quote.objects.get(pk=self.pk)
193195
if old_quote.status != "sent" and self.status == "sent":
194196
create_follow_up = True
197+
self.sent_at = timezone.now()
195198
except Quote.DoesNotExist:
196199
pass
197200

@@ -383,6 +386,19 @@ def is_invoice(self):
383386
return self.status in ["invoice", "invoice_viewed"]
384387

385388

389+
class QuoteVisit(models.Model):
390+
quote = models.ForeignKey(Quote, on_delete=models.CASCADE, related_name="visits")
391+
visited_at = models.DateTimeField(auto_now_add=True)
392+
ip_address = models.GenericIPAddressField(null=True, blank=True)
393+
user_agent = models.TextField(blank=True)
394+
395+
class Meta:
396+
ordering = ["-visited_at"]
397+
398+
def __str__(self):
399+
return f"Visit to {self.quote} at {self.visited_at:%Y-%m-%d %H:%M:%S}"
400+
401+
386402
class QuoteItem(models.Model):
387403
quote = models.ForeignKey(Quote, on_delete=models.CASCADE, related_name="items")
388404
service = models.ForeignKey(Service, on_delete=models.PROTECT)

quotes/templates/pages/privacy-policy.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ <h2>{% trans 'Contact Us' %}</h2>
9595
{% trans 'If you have any questions about this Privacy Policy or wish to exercise any of your rights regarding your personal information, please contact us at:' %}
9696
</p>
9797
<p>
98-
{% trans 'Phone:' %} <a href="tel:+14189554461">(418) 955-4461</a><br>
98+
{% trans 'Phone:' %} <a href="tel:+18192530599">(819) 253-0599</a><br>
9999
{% trans 'Email:' %} <a href="mailto:contact@titanparking.ca">contact@titanparking.ca</a>
100100
</p>
101101
</div>

quotes/templates/partials/home/contact.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ <h3 class="contact-heading">{% trans 'Contact Information' %}</h3>
3333
</div>
3434
<div class="contact-text">
3535
<h4 class="contact-label">{% trans 'Phone' %}</h4>
36-
<a href="tel:+14189554461" class="contact-value">
37-
(418) 955-4461
36+
<a href="tel:+18192530599" class="contact-value">
37+
(819) 253-0599
3838
</a>
3939
</div>
4040
</div>
@@ -85,7 +85,7 @@ <h4 class="contact-label">{% trans 'Business Hours' %}</h4>
8585
<div class="contact-glass-panel">
8686
<h3 class="contact-job-heading">{% trans 'Looking for a job?' %}</h3>
8787
<p class="contact-job-text">{% trans 'We are always looking for new employees. Please contact us to discuss your needs.' %}</p>
88-
<a href="tel:+14189554461" class="contact-button-secondary">
88+
<a href="tel:+18192530599" class="contact-button-secondary">
8989
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="contact-button-icon">
9090
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
9191
</svg>

quotes/templates/partials/home/footer.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ <h3 class="footer-heading">{% trans 'Contact Info' %}</h3>
8383
<p class="address-line">Canada</p>
8484
</address>
8585
<p class="contact-line">
86-
<a href="tel:+14189554461" class="footer-link">
87-
(418) 955-4461
86+
<a href="tel:+18192530599" class="footer-link">
87+
(819) 253-0599
8888
</a>
8989
</p>
9090
<p class="contact-line">

quotes/templates/partials/home/header.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ <h1 class="logo-text">
7474
</form>
7575
</div>
7676

77-
<button class="button-primary" onclick="document.getElementById('desktop-phone-text').textContent = '+1 (418) 955-4461'">
77+
<button class="button-primary" onclick="document.getElementById('desktop-phone-text').textContent = '+1 (819) 253-0599'">
7878
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-phone">
7979
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
8080
</svg>
@@ -152,7 +152,7 @@ <h1 class="logo-text">
152152
{% endif %}
153153
</form>
154154
</div>
155-
<a href="tel:+14189554461" class="button-primary mobile-call">
155+
<a href="tel:+18192530599" class="button-primary mobile-call">
156156
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-phone">
157157
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
158158
</svg>

quotes/templates/partials/schema/base.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"description": "Professional parking lot striping services throughout Quebec province",
1010
{% endif %}
1111
"image": "https://titanparking.ca/static/images/logo-dark.png",
12-
"telephone": "+14189554461",
12+
"telephone": "+18192530599",
1313
"email": "contact@titanparking.ca",
1414
"address": {
1515
"@type": "PostalAddress",

quotes/templates/partials/schema/custom-layout-design.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"@type": "Organization",
1010
"name": "Titan Parking",
1111
"url": "https://titanparking.ca",
12-
"telephone": "+14189554461",
12+
"telephone": "+18192530599",
1313
"email": "contact@titanparking.ca",
1414
"address": {
1515
"@type": "PostalAddress",

quotes/templates/partials/schema/indoor-markings.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"@type": "Organization",
1010
"name": "Titan Parking",
1111
"url": "https://titanparking.ca",
12-
"telephone": "+14189554461",
12+
"telephone": "+18192530599",
1313
"email": "contact@titanparking.ca",
1414
"address": {
1515
"@type": "PostalAddress",

0 commit comments

Comments
 (0)