EL GHAFRAOUI AYOUB commited on
Commit
b636606
·
1 Parent(s): d749575
app/services/__pycache__/invoice_service.cpython-312.pyc CHANGED
Binary files a/app/services/__pycache__/invoice_service.cpython-312.pyc and b/app/services/__pycache__/invoice_service.cpython-312.pyc differ
 
app/services/invoice_service.py CHANGED
@@ -26,9 +26,10 @@ class InvoiceService:
26
  MARGIN = 30
27
  LINE_HEIGHT = 20
28
  BOX_PADDING = 10
 
29
 
30
  # Helper function to draw centered text
31
- def draw_centered_text(pdf, text, x, y, width, font="Helvetica", size=10):
32
  text_width = pdf.stringWidth(text, font, size)
33
  pdf.drawString(x + (width - text_width) / 2, y, text)
34
 
@@ -52,9 +53,9 @@ class InvoiceService:
52
  pdf.drawImage(logo_path, MARGIN, top_margin + 30, width=100, height=60)
53
 
54
  # Right side: DEVIS and Client Box - moved far right and up
55
- pdf.setFont("Helvetica-Bold", 48)
56
  devis_text = "DEVIS"
57
- devis_width = pdf.stringWidth(devis_text, "Helvetica-Bold", 48)
58
  devis_x = page_width - devis_width - MARGIN - 10
59
  devis_y = top_margin + 50
60
  pdf.drawString(devis_x, devis_y, devis_text)
@@ -69,7 +70,7 @@ class InvoiceService:
69
  pdf.rect(box_x, box_y, box_width, box_height, stroke=1)
70
 
71
  # Client Info
72
- pdf.setFont("Helvetica", 10)
73
  client_info = [
74
  data.client_name,
75
  data.project,
@@ -80,7 +81,7 @@ class InvoiceService:
80
  # Center and draw each line of client info
81
  line_height = box_height / (len(client_info) + 1)
82
  for i, text in enumerate(client_info):
83
- text_width = pdf.stringWidth(str(text), "Helvetica", 10)
84
  x = box_x + (box_width - text_width) / 2
85
  y = box_y + box_height - ((i + 1) * line_height)
86
  pdf.drawString(x, y, str(text))
@@ -187,7 +188,8 @@ class InvoiceService:
187
  sections = [
188
  ("POUTRELLES", "PCP"),
189
  ("HOURDIS", "HOURDIS"),
190
- ("PANNEAU TREILLIS SOUDES", "PTS")
 
191
  ]
192
 
193
  for section_title, keyword in sections:
@@ -201,11 +203,11 @@ class InvoiceService:
201
  nb_box_height = 80
202
  pdf.setFillColorRGB(*BLACK)
203
  pdf.rect(20, current_y - nb_box_height, nb_box_width, nb_box_height, stroke=1)
204
- pdf.setFont("Helvetica-Bold", 12) # Made slightly larger for better visibility
205
  pdf.drawString(30, current_y - nb_box_height + 60, "NB:")
206
 
207
  # Add the new text
208
- pdf.setFont("Helvetica", 8) # Smaller font for the note
209
  nb_text = "Toute modification apportée aux plans BA initialement fournis, entraine automatiquement la modification de ce devis."
210
  # Split text to fit in box
211
  words = nb_text.split()
@@ -215,7 +217,7 @@ class InvoiceService:
215
  for word in words:
216
  current_line.append(word)
217
  # Check if current line width exceeds box width
218
- if pdf.stringWidth(' '.join(current_line), "Helvetica", 8) > nb_box_width - 20:
219
  current_line.pop() # Remove last word
220
  lines.append(' '.join(current_line))
221
  current_line = [word]
@@ -245,9 +247,10 @@ class InvoiceService:
245
  pdf.drawRightString(totals_x + totals_table_width - 10, y + 6, value)
246
 
247
  # Footer
248
- pdf.setFont("Helvetica", 8)
249
  footer_text = "Douar Ait Laarassi Tidili, Cercle El Kelâa, Route de Safi, Km 14-40000 Marrakech"
250
- pdf.drawCentredString(page_width / 2, 30, footer_text)
 
251
 
252
  # Add commercial info to footer
253
  print(f"Commercial value: {data.commercial}") # Add this debug line
 
26
  MARGIN = 30
27
  LINE_HEIGHT = 20
28
  BOX_PADDING = 10
29
+ STANDARD_FONT_SIZE = 10 # Add standard font size constant
30
 
31
  # Helper function to draw centered text
32
+ def draw_centered_text(pdf, text, x, y, width, font="Helvetica", size=STANDARD_FONT_SIZE):
33
  text_width = pdf.stringWidth(text, font, size)
34
  pdf.drawString(x + (width - text_width) / 2, y, text)
35
 
 
53
  pdf.drawImage(logo_path, MARGIN, top_margin + 30, width=100, height=60)
54
 
55
  # Right side: DEVIS and Client Box - moved far right and up
56
+ pdf.setFont("Helvetica-Bold", 36) # Reduced from 48 to be more consistent
57
  devis_text = "DEVIS"
58
+ devis_width = pdf.stringWidth(devis_text, "Helvetica-Bold", 36)
59
  devis_x = page_width - devis_width - MARGIN - 10
60
  devis_y = top_margin + 50
61
  pdf.drawString(devis_x, devis_y, devis_text)
 
70
  pdf.rect(box_x, box_y, box_width, box_height, stroke=1)
71
 
72
  # Client Info
73
+ pdf.setFont("Helvetica", STANDARD_FONT_SIZE)
74
  client_info = [
75
  data.client_name,
76
  data.project,
 
81
  # Center and draw each line of client info
82
  line_height = box_height / (len(client_info) + 1)
83
  for i, text in enumerate(client_info):
84
+ text_width = pdf.stringWidth(str(text), "Helvetica", STANDARD_FONT_SIZE)
85
  x = box_x + (box_width - text_width) / 2
86
  y = box_y + box_height - ((i + 1) * line_height)
87
  pdf.drawString(x, y, str(text))
 
188
  sections = [
189
  ("POUTRELLES", "PCP"),
190
  ("HOURDIS", "HOURDIS"),
191
+ ("PANNEAU TREILLIS SOUDES", "PTS"),
192
+ ("AGGLOS", "AGGLOS")
193
  ]
194
 
195
  for section_title, keyword in sections:
 
203
  nb_box_height = 80
204
  pdf.setFillColorRGB(*BLACK)
205
  pdf.rect(20, current_y - nb_box_height, nb_box_width, nb_box_height, stroke=1)
206
+ pdf.setFont("Helvetica-Bold", STANDARD_FONT_SIZE)
207
  pdf.drawString(30, current_y - nb_box_height + 60, "NB:")
208
 
209
  # Add the new text
210
+ pdf.setFont("Helvetica", STANDARD_FONT_SIZE)
211
  nb_text = "Toute modification apportée aux plans BA initialement fournis, entraine automatiquement la modification de ce devis."
212
  # Split text to fit in box
213
  words = nb_text.split()
 
217
  for word in words:
218
  current_line.append(word)
219
  # Check if current line width exceeds box width
220
+ if pdf.stringWidth(' '.join(current_line), "Helvetica", STANDARD_FONT_SIZE) > nb_box_width - 20:
221
  current_line.pop() # Remove last word
222
  lines.append(' '.join(current_line))
223
  current_line = [word]
 
247
  pdf.drawRightString(totals_x + totals_table_width - 10, y + 6, value)
248
 
249
  # Footer
250
+ pdf.setFont("Helvetica", STANDARD_FONT_SIZE - 3)
251
  footer_text = "Douar Ait Laarassi Tidili, Cercle El Kelâa, Route de Safi, Km 14-40000 Marrakech"
252
+ pdf.drawCentredString(page_width / 2 + 20, 30, footer_text)
253
+
254
 
255
  # Add commercial info to footer
256
  print(f"Commercial value: {data.commercial}") # Add this debug line
app/templates/index copy.html ADDED
@@ -0,0 +1,749 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Invoice Generator</title>
7
+ <!-- Bootstrap CSS -->
8
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ <style>
10
+ :root {
11
+ --primary-color: #2c3e50;
12
+ --secondary-color: #34495e;
13
+ --accent-color: #3498db;
14
+ --light-gray: #f8f9fa;
15
+ --border-radius: 8px;
16
+ }
17
+
18
+ body {
19
+ background-color: #f5f6fa;
20
+ color: var(--primary-color);
21
+ }
22
+
23
+ .container {
24
+ max-width: 1400px;
25
+ padding: 2rem;
26
+ }
27
+
28
+ .card {
29
+ border: none;
30
+ box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
31
+ border-radius: var(--border-radius);
32
+ margin-bottom: 1.5rem;
33
+ }
34
+
35
+ .card-header {
36
+ background-color: var(--primary-color);
37
+ color: white;
38
+ border-radius: var(--border-radius) var(--border-radius) 0 0 !important;
39
+ padding: 1rem;
40
+ font-weight: 500;
41
+ }
42
+
43
+ .card-body {
44
+ padding: 1.5rem;
45
+ }
46
+
47
+ .form-control {
48
+ border: 1px solid #dee2e6;
49
+ border-radius: var(--border-radius);
50
+ padding: 0.75rem;
51
+ transition: all 0.3s ease;
52
+ }
53
+
54
+ .form-control:focus {
55
+ border-color: var(--accent-color);
56
+ box-shadow: 0 0 0 0.2rem rgba(52, 152, 219, 0.25);
57
+ }
58
+
59
+ .btn-primary {
60
+ background-color: var(--accent-color);
61
+ border: none;
62
+ padding: 0.75rem 1.5rem;
63
+ border-radius: var(--border-radius);
64
+ transition: all 0.3s ease;
65
+ }
66
+
67
+ .btn-primary:hover {
68
+ background-color: #2980b9;
69
+ transform: translateY(-1px);
70
+ }
71
+
72
+ .btn-success {
73
+ background-color: #2ecc71;
74
+ border: none;
75
+ padding: 1rem 2rem;
76
+ border-radius: var(--border-radius);
77
+ font-weight: 500;
78
+ transition: all 0.3s ease;
79
+ }
80
+
81
+ .btn-success:hover {
82
+ background-color: #27ae60;
83
+ transform: translateY(-2px);
84
+ }
85
+
86
+ .table {
87
+ background-color: white;
88
+ border-radius: var(--border-radius);
89
+ overflow: hidden;
90
+ }
91
+
92
+ .table-primary {
93
+ background-color: var(--primary-color);
94
+ color: white;
95
+ }
96
+
97
+ .table th {
98
+ font-weight: 500;
99
+ padding: 1rem;
100
+ }
101
+
102
+ .table td {
103
+ padding: 0.75rem;
104
+ vertical-align: middle;
105
+ }
106
+
107
+ .section-title {
108
+ background-color: var(--secondary-color);
109
+ color: white;
110
+ padding: 1rem;
111
+ margin: 2rem 0 1rem;
112
+ border-radius: var(--border-radius);
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: space-between;
116
+ }
117
+
118
+ .section-title h4 {
119
+ margin: 0;
120
+ font-weight: 500;
121
+ }
122
+
123
+ .btn-group {
124
+ background-color: white;
125
+ border-radius: var(--border-radius);
126
+ padding: 0.25rem;
127
+ }
128
+
129
+ .btn-check + .btn-outline-primary {
130
+ color: var(--primary-color);
131
+ border-color: var(--primary-color);
132
+ }
133
+
134
+ .btn-check:checked + .btn-outline-primary {
135
+ background-color: var(--primary-color);
136
+ color: white;
137
+ }
138
+
139
+ .modal-content {
140
+ border-radius: var(--border-radius);
141
+ border: none;
142
+ box-shadow: 0 5px 25px rgba(0, 0, 0, 0.2);
143
+ }
144
+
145
+ .modal-header {
146
+ background-color: var(--primary-color);
147
+ color: white;
148
+ border-radius: var(--border-radius) var(--border-radius) 0 0;
149
+ }
150
+
151
+ .btn-close {
152
+ filter: brightness(0) invert(1);
153
+ }
154
+
155
+ .btn-danger {
156
+ background-color: #e74c3c;
157
+ border: none;
158
+ border-radius: var(--border-radius);
159
+ transition: all 0.3s ease;
160
+ }
161
+
162
+ .btn-danger:hover {
163
+ background-color: #c0392b;
164
+ }
165
+
166
+ /* Responsive adjustments */
167
+ @media (max-width: 768px) {
168
+ .container {
169
+ padding: 1rem;
170
+ }
171
+
172
+ .card-body {
173
+ padding: 1rem;
174
+ }
175
+
176
+ .btn {
177
+ padding: 0.5rem 1rem;
178
+ }
179
+ }
180
+ </style>
181
+ </head>
182
+ <body>
183
+ <div class="container mt-4">
184
+ <h2 class="mb-4">Générateur de Devis</h2>
185
+
186
+ <form id="invoiceForm">
187
+ <!-- Client Information -->
188
+ <div class="row mb-4">
189
+ <div class="col-md-6">
190
+ <div class="card">
191
+ <div class="card-header">
192
+ Information Client
193
+ </div>
194
+ <div class="card-body">
195
+ <div class="mb-3">
196
+ <label for="clientName" class="form-label">Nom du Client</label>
197
+ <input type="text" class="form-control" id="clientName" required>
198
+ </div>
199
+ <div class="mb-3">
200
+ <label for="clientPhone" class="form-label">Téléphone</label>
201
+ <input type="tel" class="form-control" id="clientPhone" required>
202
+ </div>
203
+ <div class="mb-3">
204
+ <label for="clientAddress" class="form-label">Adresse</label>
205
+ <input type="text" class="form-control" id="clientAddress" required>
206
+ </div>
207
+ <div class="mb-3">
208
+ <label class="form-label">Type Client</label>
209
+ <div class="btn-group" role="group">
210
+ <input type="radio" class="btn-check" name="clientType" id="EE" value="EE" autocomplete="off">
211
+ <label class="btn btn-outline-primary" for="EE">EE</label>
212
+
213
+ <input type="radio" class="btn-check" name="clientType" id="MED" value="MED" autocomplete="off">
214
+ <label class="btn btn-outline-primary" for="MED">MED</label>
215
+
216
+ <input type="radio" class="btn-check" name="clientType" id="AM" value="AM" autocomplete="off">
217
+ <label class="btn btn-outline-primary" for="AM">AM</label>
218
+
219
+ <input type="radio" class="btn-check" name="clientType" id="DIV" value="DIV" autocomplete="off">
220
+ <label class="btn btn-outline-primary" for="DIV">DIV</label>
221
+ </div>
222
+ </div>
223
+ <div class="mb-3">
224
+ <label class="form-label">Commercial</label>
225
+ <select class="form-control" id="commercial" required>
226
+ <option value="">Sélectionner un commercial</option>
227
+ <option value="khaled">Khaled</option>
228
+ <option value="salah">Salah</option>
229
+ <option value="ismail">Ismail</option>
230
+ <option value="jamal">Jamal</option>
231
+ <option value="divers">Divers</option>
232
+ </select>
233
+ </div>
234
+ </div>
235
+ </div>
236
+ </div>
237
+
238
+ <div class="col-md-6">
239
+ <div class="card">
240
+ <div class="card-header">
241
+ Information Devis
242
+ </div>
243
+ <div class="card-body">
244
+ <div class="mb-3">
245
+ <label for="invoiceNumber" class="form-label">N° Devis</label>
246
+ <input type="text" class="form-control" id="invoiceNumber" required>
247
+ </div>
248
+ <div class="mb-3">
249
+ <label for="date" class="form-label">Date</label>
250
+ <input type="date" class="form-control" id="date" readonly>
251
+ </div>
252
+ <div class="mb-3">
253
+ <label for="project" class="form-label">Type d'ouvrage</label>
254
+ <input type="text" class="form-control" id="project" required>
255
+ </div>
256
+ </div>
257
+ </div>
258
+ </div>
259
+ </div>
260
+
261
+ <!-- Poutrelles Section -->
262
+ <div class="section-title d-flex justify-content-between align-items-center">
263
+ <h4>POUTRELLES</h4>
264
+ <button type="button" class="btn btn-primary" id="addPoutrelles">
265
+ Ajouter Poutrelle
266
+ </button>
267
+ </div>
268
+ <div class="table-responsive">
269
+ <table class="table table-bordered table-fixed">
270
+ <thead class="table-primary">
271
+ <tr>
272
+ <th style="width: 30%">Description</th>
273
+ <th style="width: 10%">Unité</th>
274
+ <th style="width: 10%">Quantité</th>
275
+ <th style="width: 15%">Longueur</th>
276
+ <th style="width: 15%">P.U</th>
277
+ <th style="width: 15%">Total HT</th>
278
+ <th style="width: 5%"></th>
279
+ </tr>
280
+ </thead>
281
+ <tbody id="poutrellesTable"></tbody>
282
+ </table>
283
+ </div>
284
+
285
+ <!-- Hourdis Section -->
286
+ <div class="section-title d-flex justify-content-between align-items-center">
287
+ <h4>HOURDIS</h4>
288
+ <button type="button" class="btn btn-primary" id="addHourdis">
289
+ Ajouter Hourdis
290
+ </button>
291
+ </div>
292
+ <div class="table-responsive">
293
+ <table class="table table-bordered table-fixed">
294
+ <thead class="table-primary">
295
+ <tr>
296
+ <th style="width: 30%">Description</th>
297
+ <th style="width: 10%">Unité</th>
298
+ <th style="width: 10%">Quantité</th>
299
+ <th style="width: 15%">Longueur</th>
300
+ <th style="width: 15%">P.U</th>
301
+ <th style="width: 15%">Total HT</th>
302
+ <th style="width: 5%"></th>
303
+ </tr>
304
+ </thead>
305
+ <tbody id="hourdisTable"></tbody>
306
+ </table>
307
+ </div>
308
+
309
+ <!-- Panneau Section -->
310
+ <div class="section-title d-flex justify-content-between align-items-center">
311
+ <h4>PANNEAU TREILLIS SOUDES</h4>
312
+ <button type="button" class="btn btn-primary" id="addPanneau">
313
+ Ajouter Panneau
314
+ </button>
315
+ </div>
316
+ <div class="table-responsive">
317
+ <table class="table table-bordered table-fixed">
318
+ <thead class="table-primary">
319
+ <tr>
320
+ <th style="width: 30%">Description</th>
321
+ <th style="width: 10%">Unité</th>
322
+ <th style="width: 10%">Quantité</th>
323
+ <th style="width: 15%">Longueur</th>
324
+ <th style="width: 15%">P.U</th>
325
+ <th style="width: 15%">Total HT</th>
326
+ <th style="width: 5%"></th>
327
+ </tr>
328
+ </thead>
329
+ <tbody id="panneauTable"></tbody>
330
+ </table>
331
+ </div>
332
+
333
+ <!-- Totals -->
334
+ <div class="row mt-4">
335
+ <div class="col-md-6">
336
+ <!-- Empty for spacing -->
337
+ </div>
338
+ <div class="col-md-6">
339
+ <div class="card">
340
+ <div class="card-body">
341
+ <div class="mb-3">
342
+ <label for="totalHT" class="form-label">Total HT</label>
343
+ <input type="number" class="form-control" id="totalHT" readonly>
344
+ </div>
345
+ <div class="mb-3">
346
+ <label for="tax" class="form-label">TVA 20%</label>
347
+ <input type="number" class="form-control" id="tax" readonly>
348
+ </div>
349
+ <div class="mb-3">
350
+ <label for="totalTTC" class="form-label">Total TTC</label>
351
+ <input type="number" class="form-control" id="totalTTC" readonly>
352
+ </div>
353
+ </div>
354
+ </div>
355
+ </div>
356
+ </div>
357
+
358
+ <div class="d-grid gap-2 col-md-6 mx-auto mt-4">
359
+ <button type="submit" class="btn btn-success btn-lg">Générer le Devis</button>
360
+ </div>
361
+ </form>
362
+ </div>
363
+
364
+ <!-- Poutrelles Modal -->
365
+ <div class="modal fade" id="poutrellesModal" tabindex="-1">
366
+ <div class="modal-dialog modal-lg">
367
+ <div class="modal-content">
368
+ <div class="modal-header">
369
+ <h5 class="modal-title">Sélectionner Poutrelle</h5>
370
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
371
+ </div>
372
+ <div class="modal-body">
373
+ <div class="table-responsive">
374
+ <table class="table">
375
+ <thead>
376
+ <tr>
377
+ <th>Description</th>
378
+ <th>Unité</th>
379
+ <th>Quantité</th>
380
+ <th>Longueur</th>
381
+ <th>P.U</th>
382
+ <th>Action</th>
383
+ </tr>
384
+ </thead>
385
+ <tbody></tbody>
386
+ </table>
387
+ </div>
388
+ </div>
389
+ </div>
390
+ </div>
391
+ </div>
392
+
393
+ <!-- Hourdis Modal -->
394
+ <div class="modal fade" id="hourdisModal" tabindex="-1">
395
+ <div class="modal-dialog modal-lg">
396
+ <div class="modal-content">
397
+ <div class="modal-header">
398
+ <h5 class="modal-title">Sélectionner Hourdis</h5>
399
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
400
+ </div>
401
+ <div class="modal-body">
402
+ <div class="table-responsive">
403
+ <table class="table">
404
+ <thead>
405
+ <tr>
406
+ <th>Description</th>
407
+ <th>Unité</th>
408
+ <th>Quantité</th>
409
+ <th>Longueur</th>
410
+ <th>P.U</th>
411
+ <th>Action</th>
412
+ </tr>
413
+ </thead>
414
+ <tbody></tbody>
415
+ </table>
416
+ </div>
417
+ </div>
418
+ </div>
419
+ </div>
420
+ </div>
421
+
422
+ <!-- Panneau Modal -->
423
+ <div class="modal fade" id="panneauModal" tabindex="-1">
424
+ <div class="modal-dialog modal-lg">
425
+ <div class="modal-content">
426
+ <div class="modal-header">
427
+ <h5 class="modal-title">Sélectionner Panneau</h5>
428
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
429
+ </div>
430
+ <div class="modal-body">
431
+ <div class="table-responsive">
432
+ <table class="table">
433
+ <thead>
434
+ <tr>
435
+ <th>Description</th>
436
+ <th>Unité</th>
437
+ <th>Quantité</th>
438
+ <th>Longueur</th>
439
+ <th>P.U</th>
440
+ <th>Action</th>
441
+ </tr>
442
+ </thead>
443
+ <tbody></tbody>
444
+ </table>
445
+ </div>
446
+ </div>
447
+ </div>
448
+ </div>
449
+ </div>
450
+
451
+ <!-- Bootstrap JS -->
452
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
453
+
454
+ <script>
455
+ document.addEventListener("DOMContentLoaded", function() {
456
+ const poutrellesTable = document.getElementById("poutrellesTable");
457
+ const hourdisTable = document.getElementById("hourdisTable");
458
+ const panneauTable = document.getElementById("panneauTable");
459
+ const invoiceForm = document.getElementById("invoiceForm");
460
+
461
+ // Default items data
462
+ const defaultItems = {
463
+ poutrelles: [
464
+ { description: "PCP 114N", unit: "ML", quantity: 21, length: 3.00, unit_price: 26.00 },
465
+ { description: "PCP 113N", unit: "ML", quantity: 12, length: 4.00, unit_price: 26.00 }
466
+ ],
467
+ hourdis: [
468
+ { description: "HOURDIS TYPE 12", unit: "U", quantity: 1, length: 300, unit_price: 3.50 }
469
+ ],
470
+ panneaux: [
471
+ { description: "PTS SISMIQUE 5*3,5", unit: "U", quantity: 1, length: 9, unit_price: 195.00 }
472
+ ]
473
+ };
474
+
475
+ // Function to add a new item row
476
+ function addItemRow(item, tableBody) {
477
+ const row = document.createElement("tr");
478
+ const total = (item.quantity * item.length * item.unit_price).toFixed(2);
479
+
480
+ row.innerHTML = `
481
+ <td><input type="text" class="form-control" name="description" value="${item.description}" readonly></td>
482
+ <td><input type="text" class="form-control" name="unit" value="${item.unit}" readonly></td>
483
+ <td><input type="number" class="form-control" name="quantity" value="${item.quantity}" required></td>
484
+ <td><input type="number" class="form-control" name="length" value="${item.length}" step="0.01" required></td>
485
+ <td><input type="number" class="form-control" name="unitPrice" value="${item.unit_price}" step="0.01" required></td>
486
+ <td><input type="number" class="form-control" name="totalHT" value="${total}" readonly></td>
487
+ <td><button type="button" class="btn btn-danger btn-sm remove-item">×</button></td>
488
+ `;
489
+
490
+ row.querySelector(".remove-item").addEventListener("click", () => {
491
+ row.remove();
492
+ updateTotals();
493
+ });
494
+
495
+ // Add input event listeners to update total
496
+ ['quantity', 'length', 'unitPrice'].forEach(field => {
497
+ row.querySelector(`input[name='${field}']`).addEventListener('input', () => {
498
+ const qty = parseFloat(row.querySelector('input[name="quantity"]').value) || 0;
499
+ const length = parseFloat(row.querySelector('input[name="length"]').value) || 0;
500
+ const price = parseFloat(row.querySelector('input[name="unitPrice"]').value) || 0;
501
+ const rowTotal = (qty * length * price).toFixed(2);
502
+ row.querySelector('input[name="totalHT"]').value = rowTotal;
503
+ updateTotals();
504
+ });
505
+ });
506
+
507
+ tableBody.appendChild(row);
508
+ updateTotals();
509
+ }
510
+
511
+ // Function to update totals
512
+ function updateTotals() {
513
+ const totalHT = Array.from(document.querySelectorAll('input[name="totalHT"]'))
514
+ .reduce((sum, input) => sum + (parseFloat(input.value) || 0), 0);
515
+
516
+ const formattedTotalHT = totalHT.toFixed(2);
517
+ const tax = (totalHT * 0.20).toFixed(2);
518
+ const totalTTC = (totalHT * 1.20).toFixed(2);
519
+
520
+ document.querySelector("#totalHT").value = formattedTotalHT;
521
+ document.querySelector("#tax").value = tax;
522
+ document.querySelector("#totalTTC").value = totalTTC;
523
+ }
524
+
525
+ // Initialize tables with default items
526
+ defaultItems.poutrelles.forEach(item => addItemRow(item, poutrellesTable));
527
+ defaultItems.hourdis.forEach(item => addItemRow(item, hourdisTable));
528
+ defaultItems.panneaux.forEach(item => addItemRow(item, panneauTable));
529
+
530
+ // Update initial totals
531
+ updateTotals();
532
+
533
+ // Predefined items data
534
+ const predefinedItems = {
535
+ poutrelles: [
536
+ { description: "PCP 114N", unit: "ML", quantity: 21, length: 3.00, unit_price: 26.00 },
537
+ { description: "PCP 113N", unit: "ML", quantity: 12, length: 4.00, unit_price: 26.00 },
538
+ { description: "PCP 113B SISMIQUE", unit: "ML", quantity: 22, length: 4.20, unit_price: 26.00 },
539
+ { description: "PCP 114B SISMIQUE", unit: "ML", quantity: 12, length: 4.90, unit_price: 30.00 },
540
+ { description: "PCP 135 SISMIQUE", unit: "ML", quantity: 8, length: 5.00, unit_price: 38.00 },
541
+ { description: "PCP 156 SISMIQUE", unit: "ML", quantity: 12, length: 5.10, unit_price: 38.00 },
542
+ { description: "PCP 158 SISMIQUE", unit: "ML", quantity: 12, length: 5.50, unit_price: 51.00 }
543
+ ],
544
+ hourdis: [
545
+ { description: "HOURDIS TYPE 08", unit: "U", quantity: 1, length: 200, unit_price: 3.40 },
546
+ { description: "HOURDIS TYPE 12", unit: "U", quantity: 1, length: 300, unit_price: 3.50 },
547
+ { description: "HOURDIS TYPE 16", unit: "U", quantity: 1, length: 0, unit_price: 0 },
548
+ { description: "HOURDIS TYPE 20", unit: "U", quantity: 1, length: 0, unit_price: 0 },
549
+ { description: "HOURDIS TYPE 25", unit: "U", quantity: 1, length: 450, unit_price: 4.80 },
550
+ { description: "HOURDIS TYPE 30", unit: "U", quantity: 1, length: 0, unit_price: 0 }
551
+ ],
552
+ panneaux: [
553
+ { description: "PTS Normal 3,5*3,5", unit: "U", quantity: 1, length: 0, unit_price: 0 },
554
+ { description: "PTS SISMIQUE 5*3,5", unit: "U", quantity: 1, length: 9, unit_price: 195.00 }
555
+ ]
556
+ };
557
+
558
+ // Function to populate modal with items
559
+ function populateModal(modalId, items, targetTable) {
560
+ const modal = document.querySelector(modalId);
561
+ const tbody = modal.querySelector('tbody');
562
+ tbody.innerHTML = '';
563
+
564
+ items.forEach(item => {
565
+ const row = document.createElement('tr');
566
+ row.innerHTML = `
567
+ <td>${item.description}</td>
568
+ <td>${item.unit}</td>
569
+ <td><input type="number" class="form-control form-control-sm" value="${item.quantity}" name="modal-quantity"></td>
570
+ <td><input type="number" class="form-control form-control-sm" value="${item.length}" step="0.01" name="modal-length"></td>
571
+ <td><input type="number" class="form-control form-control-sm" value="${item.unit_price}" step="0.01" name="modal-price"></td>
572
+ <td>
573
+ <button class="btn btn-primary btn-sm add-item">
574
+ Ajouter
575
+ </button>
576
+ </td>
577
+ `;
578
+
579
+ row.querySelector('.add-item').addEventListener('click', () => {
580
+ const newItem = {
581
+ ...item,
582
+ quantity: parseFloat(row.querySelector('[name="modal-quantity"]').value) || 0,
583
+ length: parseFloat(row.querySelector('[name="modal-length"]').value) || 0,
584
+ unit_price: parseFloat(row.querySelector('[name="modal-price"]').value) || 0
585
+ };
586
+ addItemRow(newItem, targetTable);
587
+ bootstrap.Modal.getInstance(modal).hide();
588
+ });
589
+
590
+ tbody.appendChild(row);
591
+ });
592
+ }
593
+
594
+ // Update button click handlers
595
+ document.querySelector("#addPoutrelles").addEventListener("click", () => {
596
+ populateModal('#poutrellesModal', predefinedItems.poutrelles, poutrellesTable);
597
+ new bootstrap.Modal('#poutrellesModal').show();
598
+ });
599
+
600
+ document.querySelector("#addHourdis").addEventListener("click", () => {
601
+ populateModal('#hourdisModal', predefinedItems.hourdis, hourdisTable);
602
+ new bootstrap.Modal('#hourdisModal').show();
603
+ });
604
+
605
+ document.querySelector("#addPanneau").addEventListener("click", () => {
606
+ populateModal('#panneauModal', predefinedItems.panneaux, panneauTable);
607
+ new bootstrap.Modal('#panneauModal').show();
608
+ });
609
+
610
+ // Form submission
611
+ invoiceForm.addEventListener("submit", async (event) => {
612
+ event.preventDefault();
613
+
614
+ const getAllItems = () => {
615
+ const items = [];
616
+ [poutrellesTable, hourdisTable, panneauTable].forEach(table => {
617
+ items.push(...Array.from(table.querySelectorAll("tr")).map(row => ({
618
+ description: row.querySelector("input[name='description']").value,
619
+ unit: row.querySelector("input[name='unit']").value,
620
+ quantity: parseInt(row.querySelector("input[name='quantity']").value, 10),
621
+ length: parseFloat(row.querySelector("input[name='length']").value),
622
+ unit_price: parseFloat(row.querySelector("input[name='unitPrice']").value),
623
+ total_price: parseFloat(row.querySelector("input[name='totalHT']").value)
624
+ })));
625
+ });
626
+ return items;
627
+ };
628
+
629
+ const data = {
630
+ invoice_number: document.querySelector("#invoiceNumber").value,
631
+ date: new Date(document.querySelector("#date").value).toISOString(),
632
+ project: document.querySelector("#project").value,
633
+ client_name: document.querySelector("#clientName").value,
634
+ client_phone: document.querySelector("#clientPhone").value,
635
+ address: document.querySelector("#clientAddress").value,
636
+ total_ht: parseFloat(document.querySelector("#totalHT").value || 0),
637
+ tax: parseFloat(document.querySelector("#tax").value || 0),
638
+ total_ttc: parseFloat(document.querySelector("#totalTTC").value || 0),
639
+ items: getAllItems(),
640
+ frame_number: "",
641
+ status: "pending",
642
+ created_at: new Date().toISOString(),
643
+ commercial: document.querySelector("#commercial").value || "divers"
644
+ };
645
+
646
+ console.log("Sending invoice data:", data);
647
+
648
+ try {
649
+ // First create the invoice
650
+ const createResponse = await fetch("/api/invoices/", {
651
+ method: "POST",
652
+ headers: { "Content-Type": "application/json" },
653
+ body: JSON.stringify(data)
654
+ });
655
+
656
+ if (!createResponse.ok) {
657
+ const errorData = await createResponse.json();
658
+ console.error("Server error:", errorData);
659
+ throw new Error(`Failed to create invoice: ${JSON.stringify(errorData)}`);
660
+ }
661
+
662
+ const invoice = await createResponse.json();
663
+ console.log("Created invoice:", invoice);
664
+
665
+ // Then generate the PDF
666
+ const pdfResponse = await fetch(`/api/invoices/${invoice.id}/generate-pdf`, {
667
+ method: "POST",
668
+ headers: {
669
+ "Content-Type": "application/json"
670
+ },
671
+ body: JSON.stringify(invoice)
672
+ });
673
+
674
+ if (!pdfResponse.ok) {
675
+ const errorData = await pdfResponse.text();
676
+ console.error("PDF generation error:", errorData);
677
+ throw new Error(`Failed to generate PDF: ${errorData}`);
678
+ }
679
+
680
+ // Handle PDF download
681
+ const blob = await pdfResponse.blob();
682
+ const url = window.URL.createObjectURL(blob);
683
+ const a = document.createElement("a");
684
+ a.href = url;
685
+ a.download = `devis_${invoice.invoice_number}.pdf`;
686
+ document.body.appendChild(a);
687
+ a.click();
688
+ document.body.removeChild(a);
689
+ window.URL.revokeObjectURL(url);
690
+
691
+ } catch (error) {
692
+ console.error("Error:", error);
693
+ alert(`Error: ${error.message}`);
694
+ }
695
+
696
+ // After successful submission, update the invoice number for the current type
697
+ const selectedType = document.querySelector('input[name="clientType"]:checked')?.value;
698
+ if (selectedType) {
699
+ await updateInvoiceNumber(selectedType);
700
+ }
701
+ });
702
+
703
+ // Set today's date automatically
704
+ const today = new Date();
705
+ const formattedDate = today.toISOString().split('T')[0];
706
+ document.querySelector("#date").value = formattedDate;
707
+
708
+ // Auto-generate invoice number when client type is selected
709
+ const clientTypeInputs = document.querySelectorAll('input[name="clientType"]');
710
+ const invoiceNumberInput = document.querySelector("#invoiceNumber");
711
+
712
+ // Function to get and update the invoice number
713
+ async function updateInvoiceNumber(selectedType) {
714
+ try {
715
+ // Disable the input while fetching
716
+ invoiceNumberInput.disabled = true;
717
+
718
+ const response = await fetch(`/api/invoices/last-number/${selectedType}`);
719
+ if (response.ok) {
720
+ const data = await response.json();
721
+ invoiceNumberInput.value = data.formatted_number;
722
+ } else {
723
+ throw new Error('Failed to get invoice number');
724
+ }
725
+ } catch (error) {
726
+ console.error('Error:', error);
727
+ invoiceNumberInput.value = `DCP/${selectedType}/0001`;
728
+ } finally {
729
+ // Re-enable the input
730
+ invoiceNumberInput.disabled = false;
731
+ }
732
+ }
733
+
734
+ // Update invoice number when client type is selected
735
+ clientTypeInputs.forEach(input => {
736
+ input.addEventListener('change', () => {
737
+ updateInvoiceNumber(input.value);
738
+ });
739
+ });
740
+
741
+ // Optional: Update the number when the page loads if a type is already selected
742
+ const selectedType = document.querySelector('input[name="clientType"]:checked')?.value;
743
+ if (selectedType) {
744
+ updateInvoiceNumber(selectedType);
745
+ }
746
+ });
747
+ </script>
748
+ </body>
749
+ </html>
app/templates/index.html CHANGED
@@ -7,205 +7,423 @@
7
  <!-- Bootstrap CSS -->
8
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
9
  <style>
10
- .table-fixed {
11
- table-layout: fixed;
 
 
 
 
12
  }
13
- .table-fixed td {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  overflow: hidden;
15
- text-overflow: ellipsis;
16
- white-space: nowrap;
17
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  .section-title {
19
- background-color: #f8f9fa;
20
- padding: 10px;
21
- margin-top: 20px;
22
- margin-bottom: 10px;
23
- border-radius: 5px;
 
 
 
24
  }
25
- </style>
26
- </head>
27
- <body>
28
- <div class="container mt-4">
29
- <h2 class="mb-4">Générateur de Devis</h2>
30
-
31
- <form id="invoiceForm">
32
- <!-- Client Information -->
33
- <div class="row mb-4">
34
- <div class="col-md-6">
35
- <div class="card">
36
- <div class="card-header">
37
- Information Client
38
- </div>
39
- <div class="card-body">
40
- <div class="mb-3">
41
- <label for="clientName" class="form-label">Nom du Client</label>
42
- <input type="text" class="form-control" id="clientName" required>
43
- </div>
44
- <div class="mb-3">
45
- <label for="clientPhone" class="form-label">Téléphone</label>
46
- <input type="tel" class="form-control" id="clientPhone" required>
47
- </div>
48
- <div class="mb-3">
49
- <label for="clientAddress" class="form-label">Adresse</label>
50
- <input type="text" class="form-control" id="clientAddress" required>
51
- </div>
52
- <div class="mb-3">
53
- <label class="form-label">Type Client</label>
54
- <div class="btn-group" role="group">
55
- <input type="radio" class="btn-check" name="clientType" id="EE" value="EE" autocomplete="off">
56
- <label class="btn btn-outline-primary" for="EE">EE</label>
57
 
58
- <input type="radio" class="btn-check" name="clientType" id="MED" value="MED" autocomplete="off">
59
- <label class="btn btn-outline-primary" for="MED">MED</label>
 
 
60
 
61
- <input type="radio" class="btn-check" name="clientType" id="AM" value="AM" autocomplete="off">
62
- <label class="btn btn-outline-primary" for="AM">AM</label>
 
 
 
63
 
64
- <input type="radio" class="btn-check" name="clientType" id="DIV" value="DIV" autocomplete="off">
65
- <label class="btn btn-outline-primary" for="DIV">DIV</label>
66
- </div>
67
- </div>
68
- <div class="mb-3">
69
- <label class="form-label">Commercial</label>
70
- <select class="form-control" id="commercial" required>
71
- <option value="">Sélectionner un commercial</option>
72
- <option value="khaled">Khaled</option>
73
- <option value="salah">Salah</option>
74
- <option value="ismail">Ismail</option>
75
- <option value="jamal">Jamal</option>
76
- <option value="divers">Divers</option>
77
- </select>
78
- </div>
79
- </div>
80
- </div>
81
- </div>
82
-
83
- <div class="col-md-6">
84
- <div class="card">
85
- <div class="card-header">
86
- Information Devis
87
- </div>
88
- <div class="card-body">
89
- <div class="mb-3">
90
- <label for="invoiceNumber" class="form-label">N° Devis</label>
91
- <input type="text" class="form-control" id="invoiceNumber" required>
92
- </div>
93
- <div class="mb-3">
94
- <label for="date" class="form-label">Date</label>
95
- <input type="date" class="form-control" id="date" readonly>
96
- </div>
97
- <div class="mb-3">
98
- <label for="project" class="form-label">Type d'ouvrage</label>
99
- <input type="text" class="form-control" id="project" required>
100
- </div>
101
- </div>
102
- </div>
103
- </div>
104
- </div>
105
 
106
- <!-- Poutrelles Section -->
107
- <div class="section-title d-flex justify-content-between align-items-center">
108
- <h4>POUTRELLES</h4>
109
- <button type="button" class="btn btn-primary" id="addPoutrelles">
110
- Ajouter Poutrelle
111
- </button>
112
- </div>
113
- <div class="table-responsive">
114
- <table class="table table-bordered table-fixed">
115
- <thead class="table-primary">
116
- <tr>
117
- <th style="width: 30%">Description</th>
118
- <th style="width: 10%">Unité</th>
119
- <th style="width: 10%">Quantité</th>
120
- <th style="width: 15%">Longueur</th>
121
- <th style="width: 15%">P.U</th>
122
- <th style="width: 15%">Total HT</th>
123
- <th style="width: 5%"></th>
124
- </tr>
125
- </thead>
126
- <tbody id="poutrellesTable"></tbody>
127
- </table>
128
- </div>
129
 
130
- <!-- Hourdis Section -->
131
- <div class="section-title d-flex justify-content-between align-items-center">
132
- <h4>HOURDIS</h4>
133
- <button type="button" class="btn btn-primary" id="addHourdis">
134
- Ajouter Hourdis
135
- </button>
136
- </div>
137
- <div class="table-responsive">
138
- <table class="table table-bordered table-fixed">
139
- <thead class="table-primary">
140
- <tr>
141
- <th style="width: 30%">Description</th>
142
- <th style="width: 10%">Unité</th>
143
- <th style="width: 10%">Quantité</th>
144
- <th style="width: 15%">Longueur</th>
145
- <th style="width: 15%">P.U</th>
146
- <th style="width: 15%">Total HT</th>
147
- <th style="width: 5%"></th>
148
- </tr>
149
- </thead>
150
- <tbody id="hourdisTable"></tbody>
151
- </table>
152
- </div>
153
 
154
- <!-- Panneau Section -->
155
- <div class="section-title d-flex justify-content-between align-items-center">
156
- <h4>PANNEAU TREILLIS SOUDES</h4>
157
- <button type="button" class="btn btn-primary" id="addPanneau">
158
- Ajouter Panneau
159
- </button>
160
- </div>
161
- <div class="table-responsive">
162
- <table class="table table-bordered table-fixed">
163
- <thead class="table-primary">
164
- <tr>
165
- <th style="width: 30%">Description</th>
166
- <th style="width: 10%">Unité</th>
167
- <th style="width: 10%">Quantité</th>
168
- <th style="width: 15%">Longueur</th>
169
- <th style="width: 15%">P.U</th>
170
- <th style="width: 15%">Total HT</th>
171
- <th style="width: 5%"></th>
172
- </tr>
173
- </thead>
174
- <tbody id="panneauTable"></tbody>
175
- </table>
176
- </div>
177
 
178
- <!-- Totals -->
179
- <div class="row mt-4">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  <div class="col-md-6">
181
- <!-- Empty for spacing -->
182
  </div>
183
- <div class="col-md-6">
184
- <div class="card">
185
- <div class="card-body">
186
- <div class="mb-3">
187
- <label for="totalHT" class="form-label">Total HT</label>
188
- <input type="number" class="form-control" id="totalHT" readonly>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  </div>
190
- <div class="mb-3">
191
- <label for="tax" class="form-label">TVA 20%</label>
192
- <input type="number" class="form-control" id="tax" readonly>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  </div>
194
- <div class="mb-3">
195
- <label for="totalTTC" class="form-label">Total TTC</label>
196
- <input type="number" class="form-control" id="totalTTC" readonly>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  </div>
198
- </div>
199
  </div>
200
  </div>
201
  </div>
202
-
203
- <div class="d-grid gap-2 col-md-6 mx-auto mt-4">
204
- <button type="submit" class="btn btn-success btn-lg">Générer le Devis</button>
205
- </div>
206
- </form>
207
  </div>
208
 
 
 
 
209
  <!-- Poutrelles Modal -->
210
  <div class="modal fade" id="poutrellesModal" tabindex="-1">
211
  <div class="modal-dialog modal-lg">
@@ -293,6 +511,35 @@
293
  </div>
294
  </div>
295
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  <!-- Bootstrap JS -->
297
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
298
 
@@ -301,7 +548,9 @@
301
  const poutrellesTable = document.getElementById("poutrellesTable");
302
  const hourdisTable = document.getElementById("hourdisTable");
303
  const panneauTable = document.getElementById("panneauTable");
 
304
  const invoiceForm = document.getElementById("invoiceForm");
 
305
 
306
  // Default items data
307
  const defaultItems = {
@@ -323,8 +572,8 @@
323
  const total = (item.quantity * item.length * item.unit_price).toFixed(2);
324
 
325
  row.innerHTML = `
326
- <td><input type="text" class="form-control" name="description" value="${item.description}" required></td>
327
- <td><input type="text" class="form-control" name="unit" value="${item.unit}" required></td>
328
  <td><input type="number" class="form-control" name="quantity" value="${item.quantity}" required></td>
329
  <td><input type="number" class="form-control" name="length" value="${item.length}" step="0.01" required></td>
330
  <td><input type="number" class="form-control" name="unitPrice" value="${item.unit_price}" step="0.01" required></td>
@@ -397,6 +646,14 @@
397
  panneaux: [
398
  { description: "PTS Normal 3,5*3,5", unit: "U", quantity: 1, length: 0, unit_price: 0 },
399
  { description: "PTS SISMIQUE 5*3,5", unit: "U", quantity: 1, length: 9, unit_price: 195.00 }
 
 
 
 
 
 
 
 
400
  ]
401
  };
402
 
@@ -452,13 +709,18 @@
452
  new bootstrap.Modal('#panneauModal').show();
453
  });
454
 
 
 
 
 
 
455
  // Form submission
456
  invoiceForm.addEventListener("submit", async (event) => {
457
  event.preventDefault();
458
 
459
  const getAllItems = () => {
460
  const items = [];
461
- [poutrellesTable, hourdisTable, panneauTable].forEach(table => {
462
  items.push(...Array.from(table.querySelectorAll("tr")).map(row => ({
463
  description: row.querySelector("input[name='description']").value,
464
  unit: row.querySelector("input[name='unit']").value,
@@ -550,10 +812,6 @@
550
  const formattedDate = today.toISOString().split('T')[0];
551
  document.querySelector("#date").value = formattedDate;
552
 
553
- // Auto-generate invoice number when client type is selected
554
- const clientTypeInputs = document.querySelectorAll('input[name="clientType"]');
555
- const invoiceNumberInput = document.querySelector("#invoiceNumber");
556
-
557
  // Function to get and update the invoice number
558
  async function updateInvoiceNumber(selectedType) {
559
  try {
@@ -569,7 +827,7 @@
569
  }
570
  } catch (error) {
571
  console.error('Error:', error);
572
- invoiceNumberInput.value = `DCP/${selectedType}/0001`;
573
  } finally {
574
  // Re-enable the input
575
  invoiceNumberInput.disabled = false;
@@ -577,13 +835,13 @@
577
  }
578
 
579
  // Update invoice number when client type is selected
580
- clientTypeInputs.forEach(input => {
581
  input.addEventListener('change', () => {
582
  updateInvoiceNumber(input.value);
583
  });
584
  });
585
 
586
- // Optional: Update the number when the page loads if a type is already selected
587
  const selectedType = document.querySelector('input[name="clientType"]:checked')?.value;
588
  if (selectedType) {
589
  updateInvoiceNumber(selectedType);
 
7
  <!-- Bootstrap CSS -->
8
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
9
  <style>
10
+ :root {
11
+ --primary-color: #2c3e50;
12
+ --secondary-color: #34495e;
13
+ --accent-color: #3498db;
14
+ --light-gray: #f8f9fa;
15
+ --border-radius: 8px;
16
  }
17
+
18
+ body {
19
+ background-color: #f5f6fa;
20
+ color: var(--primary-color);
21
+ }
22
+
23
+ .container {
24
+ max-width: 1400px;
25
+ padding: 2rem;
26
+ }
27
+
28
+ .card {
29
+ border: none;
30
+ box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
31
+ border-radius: var(--border-radius);
32
+ margin-bottom: 1.5rem;
33
+ }
34
+
35
+ .card-header {
36
+ background-color: var(--primary-color);
37
+ color: white;
38
+ border-radius: var(--border-radius) var(--border-radius) 0 0 !important;
39
+ padding: 1rem;
40
+ font-weight: 500;
41
+ }
42
+
43
+ .card-body {
44
+ padding: 1.5rem;
45
+ }
46
+
47
+ .form-control {
48
+ border: 1px solid #dee2e6;
49
+ border-radius: var(--border-radius);
50
+ padding: 0.75rem;
51
+ transition: all 0.3s ease;
52
+ }
53
+
54
+ .form-control:focus {
55
+ border-color: var(--accent-color);
56
+ box-shadow: 0 0 0 0.2rem rgba(52, 152, 219, 0.25);
57
+ }
58
+
59
+ .btn-primary {
60
+ background-color: var(--accent-color);
61
+ border: none;
62
+ padding: 0.75rem 1.5rem;
63
+ border-radius: var(--border-radius);
64
+ transition: all 0.3s ease;
65
+ }
66
+
67
+ .btn-primary:hover {
68
+ background-color: #2980b9;
69
+ transform: translateY(-1px);
70
+ }
71
+
72
+ .btn-success {
73
+ background-color: #2ecc71;
74
+ border: none;
75
+ padding: 1rem 2rem;
76
+ border-radius: var(--border-radius);
77
+ font-weight: 500;
78
+ transition: all 0.3s ease;
79
+ }
80
+
81
+ .btn-success:hover {
82
+ background-color: #27ae60;
83
+ transform: translateY(-2px);
84
+ }
85
+
86
+ .table {
87
+ background-color: white;
88
+ border-radius: var(--border-radius);
89
  overflow: hidden;
 
 
90
  }
91
+
92
+ .table-primary {
93
+ background-color: var(--primary-color);
94
+ color: white;
95
+ }
96
+
97
+ .table th {
98
+ font-weight: 500;
99
+ padding: 1rem;
100
+ }
101
+
102
+ .table td {
103
+ padding: 0.75rem;
104
+ vertical-align: middle;
105
+ }
106
+
107
  .section-title {
108
+ background-color: var(--secondary-color);
109
+ color: white;
110
+ padding: 1rem;
111
+ margin: 2rem 0 1rem;
112
+ border-radius: var(--border-radius);
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: space-between;
116
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
+ .section-title h4 {
119
+ margin: 0;
120
+ font-weight: 500;
121
+ }
122
 
123
+ .btn-group {
124
+ background-color: white;
125
+ border-radius: var(--border-radius);
126
+ padding: 0.25rem;
127
+ }
128
 
129
+ .btn-check + .btn-outline-primary {
130
+ color: var(--primary-color);
131
+ border-color: var(--primary-color);
132
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
+ .btn-check:checked + .btn-outline-primary {
135
+ background-color: var(--primary-color);
136
+ color: white;
137
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
+ .modal-content {
140
+ border-radius: var(--border-radius);
141
+ border: none;
142
+ box-shadow: 0 5px 25px rgba(0, 0, 0, 0.2);
143
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
+ .modal-header {
146
+ background-color: var(--primary-color);
147
+ color: white;
148
+ border-radius: var(--border-radius) var(--border-radius) 0 0;
149
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
+ .btn-close {
152
+ filter: brightness(0) invert(1);
153
+ }
154
+
155
+ .btn-danger {
156
+ background-color: #e74c3c;
157
+ border: none;
158
+ border-radius: var(--border-radius);
159
+ transition: all 0.3s ease;
160
+ }
161
+
162
+ .btn-danger:hover {
163
+ background-color: #c0392b;
164
+ }
165
+
166
+ /* Responsive adjustments */
167
+ @media (max-width: 768px) {
168
+ .container {
169
+ padding: 1rem;
170
+ }
171
+
172
+ .card-body {
173
+ padding: 1rem;
174
+ }
175
+
176
+ .btn {
177
+ padding: 0.5rem 1rem;
178
+ }
179
+ }
180
+ </style>
181
+ </head>
182
+ <body class="bg-light">
183
+ <!-- Header -->
184
+ <header class="bg-primary py-4 mb-4 shadow-sm">
185
+ <div class="container">
186
+ <div class="row align-items-center">
187
  <div class="col-md-6">
188
+ <h1 class="text-white mb-0">Générateur de Devis</h1>
189
  </div>
190
+ <div class="col-md-6 text-end">
191
+ <img src="/static/logo.png" alt="Logo" height="50" class="img-fluid">
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </header>
196
+
197
+ <div class="container">
198
+ <!-- Main Content -->
199
+ <div class="row">
200
+ <div class="col-12">
201
+ <div class="card shadow-sm mb-4">
202
+ <div class="card-body">
203
+ <form id="invoiceForm">
204
+ <!-- Client and Invoice Information -->
205
+ <div class="row mb-4">
206
+ <!-- Client Information Card -->
207
+ <div class="col-md-6">
208
+ <div class="card h-100">
209
+ <div class="card-header d-flex justify-content-between align-items-center">
210
+ <h5 class="mb-0">Information Client</h5>
211
+ <i class="fas fa-user"></i>
212
+ </div>
213
+ <div class="card-body">
214
+ <div class="mb-3">
215
+ <label for="clientName" class="form-label">Nom du Client</label>
216
+ <input type="text" class="form-control" id="clientName" required>
217
+ </div>
218
+ <div class="mb-3">
219
+ <label for="clientPhone" class="form-label">Téléphone</label>
220
+ <input type="tel" class="form-control" id="clientPhone" required>
221
+ </div>
222
+ <div class="mb-3">
223
+ <label for="clientAddress" class="form-label">Adresse</label>
224
+ <input type="text" class="form-control" id="clientAddress" required>
225
+ </div>
226
+ <div class="mb-3">
227
+ <label class="form-label">Type Client</label>
228
+ <div class="btn-group w-100" role="group">
229
+ <input type="radio" class="btn-check" name="clientType" id="EE" value="EE" autocomplete="off">
230
+ <label class="btn btn-outline-primary" for="EE">EE</label>
231
+
232
+ <input type="radio" class="btn-check" name="clientType" id="MED" value="MED" autocomplete="off">
233
+ <label class="btn btn-outline-primary" for="MED">MED</label>
234
+
235
+ <input type="radio" class="btn-check" name="clientType" id="AM" value="AM" autocomplete="off">
236
+ <label class="btn btn-outline-primary" for="AM">AM</label>
237
+
238
+ <input type="radio" class="btn-check" name="clientType" id="DIV" value="DIV" autocomplete="off">
239
+ <label class="btn btn-outline-primary" for="DIV">DIV</label>
240
+ </div>
241
+ </div>
242
+ <div class="mb-3">
243
+ <label class="form-label">Commercial</label>
244
+ <select class="form-select" id="commercial" required>
245
+ <option value="">Sélectionner un commercial</option>
246
+ <option value="khaled">Khaled</option>
247
+ <option value="salah">Salah</option>
248
+ <option value="ismail">Ismail</option>
249
+ <option value="jamal">Jamal</option>
250
+ <option value="divers">Divers</option>
251
+ </select>
252
+ </div>
253
+ </div>
254
+ </div>
255
+ </div>
256
+
257
+ <!-- Invoice Information Card -->
258
+ <div class="col-md-6">
259
+ <div class="card h-100">
260
+ <div class="card-header d-flex justify-content-between align-items-center">
261
+ <h5 class="mb-0">Information Devis</h5>
262
+ <i class="fas fa-file-invoice"></i>
263
+ </div>
264
+ <div class="card-body">
265
+ <div class="mb-3">
266
+ <label for="invoiceNumber" class="form-label">N° Devis</label>
267
+ <input type="text" class="form-control" id="invoiceNumber" required>
268
+ </div>
269
+ <div class="mb-3">
270
+ <label for="date" class="form-label">Date</label>
271
+ <input type="date" class="form-control" id="date" readonly>
272
+ </div>
273
+ <div class="mb-3">
274
+ <label for="project" class="form-label">Type d'ouvrage</label>
275
+ <input type="text" class="form-control" id="project" required>
276
+ </div>
277
+ </div>
278
+ </div>
279
+ </div>
280
  </div>
281
+
282
+ <!-- Items Sections -->
283
+ <div class="card shadow-sm mb-4">
284
+ <div class="card-body">
285
+ <!-- Poutrelles Section -->
286
+ <div class="section-title">
287
+ <h4>POUTRELLES</h4>
288
+ <button type="button" class="btn btn-light" id="addPoutrelles">
289
+ <i class="fas fa-plus"></i> Ajouter Poutrelle
290
+ </button>
291
+ </div>
292
+ <div class="table-responsive">
293
+ <table class="table table-bordered table-hover">
294
+ <thead class="table-primary">
295
+ <tr>
296
+ <th style="width: 30%">Description</th>
297
+ <th style="width: 10%">Unité</th>
298
+ <th style="width: 10%">Quantité</th>
299
+ <th style="width: 15%">Longueur</th>
300
+ <th style="width: 15%">P.U</th>
301
+ <th style="width: 15%">Total HT</th>
302
+ <th style="width: 5%"></th>
303
+ </tr>
304
+ </thead>
305
+ <tbody id="poutrellesTable"></tbody>
306
+ </table>
307
+ </div>
308
+
309
+ <!-- Hourdis Section -->
310
+ <div class="section-title mt-4">
311
+ <h4>HOURDIS</h4>
312
+ <button type="button" class="btn btn-light" id="addHourdis">
313
+ <i class="fas fa-plus"></i> Ajouter Hourdis
314
+ </button>
315
+ </div>
316
+ <div class="table-responsive">
317
+ <table class="table table-bordered table-hover">
318
+ <thead class="table-primary">
319
+ <tr>
320
+ <th style="width: 30%">Description</th>
321
+ <th style="width: 10%">Unité</th>
322
+ <th style="width: 10%">Quantité</th>
323
+ <th style="width: 15%">Longueur</th>
324
+ <th style="width: 15%">P.U</th>
325
+ <th style="width: 15%">Total HT</th>
326
+ <th style="width: 5%"></th>
327
+ </tr>
328
+ </thead>
329
+ <tbody id="hourdisTable"></tbody>
330
+ </table>
331
+ </div>
332
+
333
+ <!-- Panneau Section -->
334
+ <div class="section-title mt-4">
335
+ <h4>PANNEAU TREILLIS SOUDES</h4>
336
+ <button type="button" class="btn btn-light" id="addPanneau">
337
+ <i class="fas fa-plus"></i> Ajouter Panneau
338
+ </button>
339
+ </div>
340
+ <div class="table-responsive">
341
+ <table class="table table-bordered table-hover">
342
+ <thead class="table-primary">
343
+ <tr>
344
+ <th style="width: 30%">Description</th>
345
+ <th style="width: 10%">Unité</th>
346
+ <th style="width: 10%">Quantité</th>
347
+ <th style="width: 15%">Longueur</th>
348
+ <th style="width: 15%">P.U</th>
349
+ <th style="width: 15%">Total HT</th>
350
+ <th style="width: 5%"></th>
351
+ </tr>
352
+ </thead>
353
+ <tbody id="panneauTable"></tbody>
354
+ </table>
355
+ </div>
356
+
357
+ <!-- Agglos Section -->
358
+ <div class="section-title mt-4">
359
+ <h4>AGGLOS</h4>
360
+ <button type="button" class="btn btn-light" id="addAgglos">
361
+ <i class="fas fa-plus"></i> Ajouter Agglos
362
+ </button>
363
+ </div>
364
+ <div class="table-responsive">
365
+ <table class="table table-bordered table-hover">
366
+ <thead class="table-primary">
367
+ <tr>
368
+ <th style="width: 30%">Description</th>
369
+ <th style="width: 10%">Unité</th>
370
+ <th style="width: 10%">Quantité</th>
371
+ <th style="width: 15%">Longueur</th>
372
+ <th style="width: 15%">P.U</th>
373
+ <th style="width: 15%">Total HT</th>
374
+ <th style="width: 5%"></th>
375
+ </tr>
376
+ </thead>
377
+ <tbody id="agglosTable"></tbody>
378
+ </table>
379
+ </div>
380
+ </div>
381
  </div>
382
+
383
+ <!-- Totals Section -->
384
+ <div class="row">
385
+ <div class="col-md-6">
386
+ <!-- Empty for spacing -->
387
+ </div>
388
+ <div class="col-md-6">
389
+ <div class="card shadow-sm">
390
+ <div class="card-header">
391
+ <h5 class="mb-0">Totaux</h5>
392
+ </div>
393
+ <div class="card-body">
394
+ <div class="mb-3">
395
+ <label for="totalHT" class="form-label">Total HT</label>
396
+ <input type="number" class="form-control" id="totalHT" readonly>
397
+ </div>
398
+ <div class="mb-3">
399
+ <label for="tax" class="form-label">TVA 20%</label>
400
+ <input type="number" class="form-control" id="tax" readonly>
401
+ </div>
402
+ <div class="mb-3">
403
+ <label for="totalTTC" class="form-label">Total TTC</label>
404
+ <input type="number" class="form-control" id="totalTTC" readonly>
405
+ </div>
406
+ </div>
407
+ </div>
408
+ </div>
409
+ </div>
410
+
411
+ <!-- Submit Button -->
412
+ <div class="d-grid gap-2 col-md-6 mx-auto mt-4">
413
+ <button type="submit" class="btn btn-success btn-lg">
414
+ <i class="fas fa-file-pdf"></i> Générer le Devis
415
+ </button>
416
  </div>
417
+ </form>
418
  </div>
419
  </div>
420
  </div>
421
+ </div>
 
 
 
 
422
  </div>
423
 
424
+ <!-- Add Font Awesome for icons -->
425
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
426
+
427
  <!-- Poutrelles Modal -->
428
  <div class="modal fade" id="poutrellesModal" tabindex="-1">
429
  <div class="modal-dialog modal-lg">
 
511
  </div>
512
  </div>
513
 
514
+ <!-- Add Agglos Modal -->
515
+ <div class="modal fade" id="agglosModal" tabindex="-1">
516
+ <div class="modal-dialog modal-lg">
517
+ <div class="modal-content">
518
+ <div class="modal-header">
519
+ <h5 class="modal-title">Sélectionner Agglos</h5>
520
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
521
+ </div>
522
+ <div class="modal-body">
523
+ <div class="table-responsive">
524
+ <table class="table">
525
+ <thead>
526
+ <tr>
527
+ <th>Description</th>
528
+ <th>Unité</th>
529
+ <th>Quantité</th>
530
+ <th>Longueur</th>
531
+ <th>P.U</th>
532
+ <th>Action</th>
533
+ </tr>
534
+ </thead>
535
+ <tbody></tbody>
536
+ </table>
537
+ </div>
538
+ </div>
539
+ </div>
540
+ </div>
541
+ </div>
542
+
543
  <!-- Bootstrap JS -->
544
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
545
 
 
548
  const poutrellesTable = document.getElementById("poutrellesTable");
549
  const hourdisTable = document.getElementById("hourdisTable");
550
  const panneauTable = document.getElementById("panneauTable");
551
+ const agglosTable = document.getElementById("agglosTable");
552
  const invoiceForm = document.getElementById("invoiceForm");
553
+ const invoiceNumberInput = document.getElementById("invoiceNumber");
554
 
555
  // Default items data
556
  const defaultItems = {
 
572
  const total = (item.quantity * item.length * item.unit_price).toFixed(2);
573
 
574
  row.innerHTML = `
575
+ <td><input type="text" class="form-control" name="description" value="${item.description}" readonly></td>
576
+ <td><input type="text" class="form-control" name="unit" value="${item.unit}" readonly></td>
577
  <td><input type="number" class="form-control" name="quantity" value="${item.quantity}" required></td>
578
  <td><input type="number" class="form-control" name="length" value="${item.length}" step="0.01" required></td>
579
  <td><input type="number" class="form-control" name="unitPrice" value="${item.unit_price}" step="0.01" required></td>
 
646
  panneaux: [
647
  { description: "PTS Normal 3,5*3,5", unit: "U", quantity: 1, length: 0, unit_price: 0 },
648
  { description: "PTS SISMIQUE 5*3,5", unit: "U", quantity: 1, length: 9, unit_price: 195.00 }
649
+ ],
650
+ agglos: [
651
+ { description: "AGGLOS 07", unit: "U", quantity: 1, length: 9, unit_price: 190.00 },
652
+ { description: "AGGLOS 10", unit: "U", quantity: 1, length: 9, unit_price: 190.00 },
653
+ { description: "AGGLOS 15", unit: "U", quantity: 1, length: 9, unit_price: 190.00 },
654
+ { description: "AGGLOS 20", unit: "U", quantity: 1, length: 9, unit_price: 190.00 },
655
+ { description: "AGGLOS 25", unit: "U", quantity: 1, length: 9, unit_price: 190.00 },
656
+ { description: "AGGLOS A BRANCHER 20", unit: "U", quantity: 1, length: 9, unit_price: 190.00 }
657
  ]
658
  };
659
 
 
709
  new bootstrap.Modal('#panneauModal').show();
710
  });
711
 
712
+ document.querySelector("#addAgglos").addEventListener("click", () => {
713
+ populateModal('#agglosModal', predefinedItems.agglos, agglosTable);
714
+ new bootstrap.Modal('#agglosModal').show();
715
+ });
716
+
717
  // Form submission
718
  invoiceForm.addEventListener("submit", async (event) => {
719
  event.preventDefault();
720
 
721
  const getAllItems = () => {
722
  const items = [];
723
+ [poutrellesTable, hourdisTable, panneauTable, agglosTable].forEach(table => {
724
  items.push(...Array.from(table.querySelectorAll("tr")).map(row => ({
725
  description: row.querySelector("input[name='description']").value,
726
  unit: row.querySelector("input[name='unit']").value,
 
812
  const formattedDate = today.toISOString().split('T')[0];
813
  document.querySelector("#date").value = formattedDate;
814
 
 
 
 
 
815
  // Function to get and update the invoice number
816
  async function updateInvoiceNumber(selectedType) {
817
  try {
 
827
  }
828
  } catch (error) {
829
  console.error('Error:', error);
830
+ alert('Error getting invoice number');
831
  } finally {
832
  // Re-enable the input
833
  invoiceNumberInput.disabled = false;
 
835
  }
836
 
837
  // Update invoice number when client type is selected
838
+ document.querySelectorAll('input[name="clientType"]').forEach(input => {
839
  input.addEventListener('change', () => {
840
  updateInvoiceNumber(input.value);
841
  });
842
  });
843
 
844
+ // Update the number when the page loads if a type is already selected
845
  const selectedType = document.querySelector('input[name="clientType"]:checked')?.value;
846
  if (selectedType) {
847
  updateInvoiceNumber(selectedType);