Spaces:
Sleeping
Sleeping
EL GHAFRAOUI AYOUB
commited on
Commit
·
594b207
1
Parent(s):
85031c9
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 copy.py
ADDED
@@ -0,0 +1,308 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from reportlab.lib.pagesizes import A4
|
2 |
+
from reportlab.pdfgen import canvas
|
3 |
+
from reportlab.lib import colors
|
4 |
+
from reportlab.lib.units import cm
|
5 |
+
from io import BytesIO
|
6 |
+
from app.db.models import Invoice
|
7 |
+
import logging
|
8 |
+
import os
|
9 |
+
import datetime
|
10 |
+
|
11 |
+
# Set up logging
|
12 |
+
logger = logging.getLogger(__name__)
|
13 |
+
|
14 |
+
class InvoiceService:
|
15 |
+
@staticmethod
|
16 |
+
def generate_pdf(data: Invoice) -> bytes:
|
17 |
+
try:
|
18 |
+
buffer = BytesIO()
|
19 |
+
pdf = canvas.Canvas(buffer, pagesize=A4)
|
20 |
+
page_width, page_height = A4
|
21 |
+
|
22 |
+
# Constants
|
23 |
+
HEADER_BLUE = (0.29, 0.45, 0.68)
|
24 |
+
BLUE_LIGHT = (1.5, 1.5, 1)
|
25 |
+
WHITE = (1, 1, 1)
|
26 |
+
BLACK = (0, 0, 0)
|
27 |
+
MARGIN = 30
|
28 |
+
LINE_HEIGHT = 20
|
29 |
+
BOX_PADDING = 10
|
30 |
+
STANDARD_FONT_SIZE = 10 # Add standard font size constant
|
31 |
+
|
32 |
+
# Helper function to draw centered text
|
33 |
+
def draw_centered_text(pdf, text, x, y, width, font="Helvetica", size=STANDARD_FONT_SIZE):
|
34 |
+
text_width = pdf.stringWidth(text, font, size)
|
35 |
+
pdf.drawString(x + (width - text_width) / 2, y, text)
|
36 |
+
|
37 |
+
# Helper function to draw a bordered box
|
38 |
+
def draw_box(pdf, x, y, width, height, fill_color=None, stroke_color=BLACK):
|
39 |
+
if fill_color:
|
40 |
+
pdf.setFillColorRGB(*fill_color)
|
41 |
+
pdf.rect(x, y, width, height, fill=1, stroke=0)
|
42 |
+
pdf.setFillColorRGB(*stroke_color)
|
43 |
+
pdf.rect(x, y, width, height, stroke=1)
|
44 |
+
|
45 |
+
# Get the absolute path to the logo file
|
46 |
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
47 |
+
logo_path = os.path.join(current_dir, "..", "static", "logo.png")
|
48 |
+
|
49 |
+
# Top section layout
|
50 |
+
top_margin = page_height - 100
|
51 |
+
|
52 |
+
# Left side: Logo - moved far left and up
|
53 |
+
if os.path.exists(logo_path):
|
54 |
+
pdf.drawImage(logo_path, MARGIN, top_margin + 30, width=100, height=60)
|
55 |
+
|
56 |
+
# Right side: DEVIS and Client Box - moved far right and up
|
57 |
+
pdf.setFont("Helvetica-Bold", 36) # Reduced from 48 to be more consistent
|
58 |
+
devis_text = "DEVIS"
|
59 |
+
devis_width = pdf.stringWidth(devis_text, "Helvetica-Bold", 36)
|
60 |
+
devis_x = page_width - devis_width - MARGIN - 10
|
61 |
+
devis_y = top_margin + 50
|
62 |
+
pdf.drawString(devis_x, devis_y, devis_text)
|
63 |
+
|
64 |
+
# Client info box - moved right under DEVIS
|
65 |
+
box_width = 200
|
66 |
+
box_height = 80
|
67 |
+
box_x = page_width - box_width - MARGIN + 10
|
68 |
+
box_y = devis_y - 120
|
69 |
+
|
70 |
+
# Draw client box
|
71 |
+
pdf.rect(box_x, box_y, box_width, box_height, stroke=1)
|
72 |
+
|
73 |
+
# Client Info
|
74 |
+
pdf.setFont("Helvetica", STANDARD_FONT_SIZE)
|
75 |
+
client_info = [
|
76 |
+
data.client_name,
|
77 |
+
data.project,
|
78 |
+
data.address,
|
79 |
+
data.client_phone
|
80 |
+
]
|
81 |
+
|
82 |
+
# Center and draw each line of client info
|
83 |
+
line_height = box_height / (len(client_info) + 1)
|
84 |
+
for i, text in enumerate(client_info):
|
85 |
+
text_width = pdf.stringWidth(str(text), "Helvetica", STANDARD_FONT_SIZE)
|
86 |
+
x = box_x + (box_width - text_width) / 2
|
87 |
+
y = box_y + box_height - ((i + 1) * line_height)
|
88 |
+
pdf.drawString(x, y, str(text))
|
89 |
+
|
90 |
+
# Info boxes (Date, N° Devis, PLANCHER) - adjusted starting position
|
91 |
+
info_y = top_margin - 30
|
92 |
+
box_label_width = 120
|
93 |
+
box_value_width = 80
|
94 |
+
|
95 |
+
for label, value in [
|
96 |
+
("Date du devis :", data.date.strftime("%d/%m/%Y")),
|
97 |
+
("N° Devis :", data.invoice_number),
|
98 |
+
("PLANCHER :", data.frame_number or "PH RDC")
|
99 |
+
]:
|
100 |
+
draw_box(pdf, MARGIN, info_y, box_label_width, LINE_HEIGHT, fill_color=HEADER_BLUE)
|
101 |
+
pdf.setFillColorRGB(*WHITE)
|
102 |
+
pdf.drawString(MARGIN + BOX_PADDING, info_y + 6, label)
|
103 |
+
|
104 |
+
draw_box(pdf, MARGIN + box_label_width, info_y, box_value_width, LINE_HEIGHT, fill_color=WHITE)
|
105 |
+
pdf.setFillColorRGB(*BLACK)
|
106 |
+
draw_centered_text(pdf, str(value), MARGIN + box_label_width, info_y + 6, box_value_width)
|
107 |
+
|
108 |
+
info_y -= 25
|
109 |
+
|
110 |
+
# Table headers
|
111 |
+
table_y = info_y - 30
|
112 |
+
headers = [
|
113 |
+
("Description", 150),
|
114 |
+
("Unité", 50),
|
115 |
+
("NBRE", 50),
|
116 |
+
("LNG/Qté", 60),
|
117 |
+
("P.U", 60),
|
118 |
+
("Total HT", 170)
|
119 |
+
]
|
120 |
+
|
121 |
+
total_width = sum(width for _, width in headers)
|
122 |
+
table_x = (page_width - total_width) / 2 # Center table
|
123 |
+
draw_box(pdf, table_x, table_y, total_width, LINE_HEIGHT, fill_color=HEADER_BLUE)
|
124 |
+
pdf.setFillColorRGB(*WHITE)
|
125 |
+
# add little bit of space
|
126 |
+
|
127 |
+
|
128 |
+
current_x = table_x
|
129 |
+
for title, width in headers:
|
130 |
+
draw_box(pdf, current_x, table_y, width, LINE_HEIGHT)
|
131 |
+
pdf.setFillColorRGB(*WHITE)
|
132 |
+
draw_centered_text(pdf, title, current_x, table_y + 6, width)
|
133 |
+
current_x += width
|
134 |
+
|
135 |
+
|
136 |
+
# Draw sections and items
|
137 |
+
current_y = table_y - LINE_HEIGHT - 10
|
138 |
+
|
139 |
+
|
140 |
+
|
141 |
+
def draw_section_header2(title):
|
142 |
+
nonlocal current_y
|
143 |
+
draw_box(pdf, table_x, current_y, total_width, LINE_HEIGHT, fill_color=WHITE)
|
144 |
+
|
145 |
+
# Set the font to a bold variant
|
146 |
+
pdf.setFont("Helvetica-Bold", 9) # Adjust the font name and size as needed
|
147 |
+
|
148 |
+
# Set the fill color to black
|
149 |
+
pdf.setFillColorRGB(*BLACK) # RGB values for black
|
150 |
+
|
151 |
+
# Draw the string
|
152 |
+
pdf.drawString(table_x + BOX_PADDING, current_y + 6, title)
|
153 |
+
|
154 |
+
current_y -= LINE_HEIGHT
|
155 |
+
pdf.setFont("Helvetica", STANDARD_FONT_SIZE)
|
156 |
+
|
157 |
+
def format_currency(value):
|
158 |
+
# Format with 2 decimal places and thousands separator
|
159 |
+
return "{:,.2f}".format(value).replace(",", " ")
|
160 |
+
|
161 |
+
|
162 |
+
def draw_item_row(item, indent=False):
|
163 |
+
nonlocal current_y
|
164 |
+
pdf.setFillColorRGB(*BLACK)
|
165 |
+
current_x = table_x
|
166 |
+
|
167 |
+
draw_box(pdf, current_x, current_y, total_width, LINE_HEIGHT, fill_color=WHITE)
|
168 |
+
|
169 |
+
cells = [
|
170 |
+
(" " + item.description if indent else item.description, 150),
|
171 |
+
(item.unit, 50),
|
172 |
+
(str(item.quantity), 50),
|
173 |
+
(f"{item.length:.2f}", 60),
|
174 |
+
(f"{format_currency(item.unit_price)}", 60),
|
175 |
+
(f"{format_currency(item.total_price)} DH", 170) # Total column
|
176 |
+
]
|
177 |
+
|
178 |
+
for i, (value, width) in enumerate(cells):
|
179 |
+
draw_box(pdf, current_x, current_y, width, LINE_HEIGHT)
|
180 |
+
|
181 |
+
if i == len(cells) - 1: # If it's the last column (Total DH)
|
182 |
+
pdf.setFont("Helvetica-Bold", STANDARD_FONT_SIZE) # Make it bold
|
183 |
+
else:
|
184 |
+
pdf.setFont("Helvetica", STANDARD_FONT_SIZE) # Normal font
|
185 |
+
|
186 |
+
if isinstance(value, str) and value.startswith(" "):
|
187 |
+
pdf.drawString(current_x + 20, current_y + 6, value.strip())
|
188 |
+
else:
|
189 |
+
draw_centered_text(pdf, str(value), current_x, current_y + 6, width)
|
190 |
+
|
191 |
+
current_x += width
|
192 |
+
|
193 |
+
pdf.setFont("Helvetica", STANDARD_FONT_SIZE) # Reset font to normal for next rows
|
194 |
+
current_y -= LINE_HEIGHT
|
195 |
+
|
196 |
+
|
197 |
+
# Draw sections
|
198 |
+
sections = [
|
199 |
+
("POUTRELLES :", "PCP"),
|
200 |
+
("HOURDIS :", "HOURDIS"),
|
201 |
+
("PANNEAU TREILLIS SOUDES :", "PTS"),
|
202 |
+
("AGGLOS :", "AGGLOS")
|
203 |
+
]
|
204 |
+
|
205 |
+
for section_title, keyword in sections:
|
206 |
+
print("-------------")
|
207 |
+
print(f"section_title: {section_title}")
|
208 |
+
draw_section_header2(section_title)
|
209 |
+
items = [i for i in data.items if keyword in i.description]
|
210 |
+
for item in items:
|
211 |
+
draw_item_row(item, indent=(keyword != "lfflflflf"))
|
212 |
+
|
213 |
+
# NB box with text
|
214 |
+
nb_box_width = 200
|
215 |
+
nb_box_height = 80
|
216 |
+
pdf.setFillColorRGB(*BLACK)
|
217 |
+
pdf.rect(20, current_y - nb_box_height, nb_box_width, nb_box_height, stroke=1)
|
218 |
+
pdf.setFont("Helvetica-Bold", STANDARD_FONT_SIZE)
|
219 |
+
pdf.drawString(30, current_y - nb_box_height + 60, "NB:")
|
220 |
+
|
221 |
+
# Add the new text
|
222 |
+
pdf.setFont("Helvetica", STANDARD_FONT_SIZE)
|
223 |
+
nb_text = "Toute modification apportée aux plans BA initialement fournis, entraine automatiquement la modification de ce devis."
|
224 |
+
# Split text to fit in box
|
225 |
+
words = nb_text.split()
|
226 |
+
lines = []
|
227 |
+
current_line = []
|
228 |
+
|
229 |
+
for word in words:
|
230 |
+
current_line.append(word)
|
231 |
+
# Check if current line width exceeds box width
|
232 |
+
if pdf.stringWidth(' '.join(current_line), "Helvetica", STANDARD_FONT_SIZE) > nb_box_width - 20:
|
233 |
+
current_line.pop() # Remove last word
|
234 |
+
lines.append(' '.join(current_line))
|
235 |
+
current_line = [word]
|
236 |
+
|
237 |
+
if current_line:
|
238 |
+
lines.append(' '.join(current_line))
|
239 |
+
|
240 |
+
# Draw each line
|
241 |
+
for i, line in enumerate(lines):
|
242 |
+
pdf.drawString(30, current_y - nb_box_height + 45 - (i * 10), line)
|
243 |
+
|
244 |
+
# ADD text after the NB box
|
245 |
+
pdf.setFont("Helvetica-Bold", 9)
|
246 |
+
pdf.drawString(30 , current_y - nb_box_height - 15, "Validité du devis : 1 mois")
|
247 |
+
|
248 |
+
|
249 |
+
# Totals section
|
250 |
+
# set font to bold
|
251 |
+
pdf.setFont("Helvetica-Bold", 12)
|
252 |
+
current_y -= 20
|
253 |
+
totals_table_width = 300
|
254 |
+
row_height = 20
|
255 |
+
|
256 |
+
for i, (label1, label2, value) in enumerate([
|
257 |
+
("Total", "H.T", f"{format_currency(data.total_ht)} DH"),
|
258 |
+
("TVA", "20 %", f"{format_currency(data.tax)} DH"),
|
259 |
+
("Total", "TTC", f"{format_currency(data.total_ttc)} DH")
|
260 |
+
]):
|
261 |
+
y = current_y - (i * row_height)
|
262 |
+
totals_x = (page_width - totals_table_width) - 27
|
263 |
+
draw_box(pdf, totals_x, y, totals_table_width / 2, row_height)
|
264 |
+
draw_box(pdf, totals_x + totals_table_width / 2, y, totals_table_width / 2, row_height)
|
265 |
+
pdf.drawString(totals_x + 10, y + 6, f"{label1} {label2}")
|
266 |
+
pdf.drawRightString(totals_x + totals_table_width - 10, y + 6, value)
|
267 |
+
|
268 |
+
# Footer
|
269 |
+
pdf.setFont("Helvetica", STANDARD_FONT_SIZE - 3)
|
270 |
+
footer_text = "Douar Ait Laarassi Tidili, Cercle El Kelâa, Route de Safi, Km 14-40000 Marrakech"
|
271 |
+
pdf.drawCentredString(page_width / 2 + 20, 30, footer_text)
|
272 |
+
|
273 |
+
# add the commercial phone number
|
274 |
+
# i have salah with 0666666666 and khaled with 077777777 and ismale with 08888888 and jamal with 099999999
|
275 |
+
commercial_info = dict(
|
276 |
+
salah = "06 62 29 99 78",
|
277 |
+
khaled= "06 66 24 80 94",
|
278 |
+
ismail= "06 66 24 50 15",
|
279 |
+
jamal = "06 70 08 36 50"
|
280 |
+
)
|
281 |
+
# Add commercial info to footer
|
282 |
+
print(f"Commercial value: {data.commercial}") # Add this debug line
|
283 |
+
if data.commercial and data.commercial.lower() != 'divers':
|
284 |
+
commercial_text = f"Commercial: {data.commercial.upper()}"
|
285 |
+
commercial_phone = f"Tél: {commercial_info.get(data.commercial.lower(), '')}"
|
286 |
+
|
287 |
+
pdf.drawString(MARGIN + 10, 30, commercial_text)
|
288 |
+
#draw under the commercial text :
|
289 |
+
pdf.drawString(MARGIN + 10, 20, commercial_phone)
|
290 |
+
footer_contact = "Tél: 05 24 01 55 54 Fax : 05 24 01 55 29 E-mail : [email protected]"
|
291 |
+
pdf.drawCentredString(page_width / 2 + 10, 20, footer_contact)
|
292 |
+
## add the time and page number
|
293 |
+
|
294 |
+
pdf.drawString(page_width - 100, 30, f"Date: {datetime.datetime.now().strftime('%d/%m/%Y')}")
|
295 |
+
pdf.drawString(page_width - 100, 20, f"Page {pdf.getPageNumber()}/{pdf.getPageNumber()}")
|
296 |
+
else:
|
297 |
+
footer_contact = "Tél: 05 24 01 55 54 Fax : 05 24 01 55 29 E-mail : [email protected]"
|
298 |
+
pdf.drawCentredString(page_width / 2 + 10, 20, footer_contact)
|
299 |
+
pdf.drawString(page_width - 100, 30, f"Date: {datetime.datetime().now.strftime('%d/%m/%Y')}")
|
300 |
+
pdf.drawString(page_width - 100, 20, f"Page {pdf.getPageNumber()}/{pdf.getPageNumber()}")
|
301 |
+
|
302 |
+
pdf.save()
|
303 |
+
buffer.seek(0)
|
304 |
+
return buffer.getvalue()
|
305 |
+
|
306 |
+
except Exception as e:
|
307 |
+
logger.error(f"Error in PDF generation: {str(e)}", exc_info=True)
|
308 |
+
raise
|
app/services/invoice_service.py
CHANGED
@@ -27,7 +27,8 @@ class InvoiceService:
|
|
27 |
MARGIN = 30
|
28 |
LINE_HEIGHT = 20
|
29 |
BOX_PADDING = 10
|
30 |
-
STANDARD_FONT_SIZE = 10
|
|
|
31 |
|
32 |
# Helper function to draw centered text
|
33 |
def draw_centered_text(pdf, text, x, y, width, font="Helvetica", size=STANDARD_FONT_SIZE):
|
@@ -54,7 +55,7 @@ class InvoiceService:
|
|
54 |
pdf.drawImage(logo_path, MARGIN, top_margin + 30, width=100, height=60)
|
55 |
|
56 |
# Right side: DEVIS and Client Box - moved far right and up
|
57 |
-
pdf.setFont("Helvetica-Bold", 36)
|
58 |
devis_text = "DEVIS"
|
59 |
devis_width = pdf.stringWidth(devis_text, "Helvetica-Bold", 36)
|
60 |
devis_x = page_width - devis_width - MARGIN - 10
|
@@ -122,43 +123,29 @@ class InvoiceService:
|
|
122 |
table_x = (page_width - total_width) / 2 # Center table
|
123 |
draw_box(pdf, table_x, table_y, total_width, LINE_HEIGHT, fill_color=HEADER_BLUE)
|
124 |
pdf.setFillColorRGB(*WHITE)
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
current_x = table_x
|
129 |
for title, width in headers:
|
130 |
draw_box(pdf, current_x, table_y, width, LINE_HEIGHT)
|
131 |
pdf.setFillColorRGB(*WHITE)
|
132 |
draw_centered_text(pdf, title, current_x, table_y + 6, width)
|
133 |
current_x += width
|
134 |
|
135 |
-
|
136 |
# Draw sections and items
|
137 |
current_y = table_y - LINE_HEIGHT - 10
|
138 |
|
139 |
-
|
140 |
-
|
141 |
def draw_section_header2(title):
|
142 |
nonlocal current_y
|
143 |
draw_box(pdf, table_x, current_y, total_width, LINE_HEIGHT, fill_color=WHITE)
|
144 |
-
|
145 |
-
|
146 |
-
pdf.setFont("Helvetica-Bold", 9) # Adjust the font name and size as needed
|
147 |
-
|
148 |
-
# Set the fill color to black
|
149 |
-
pdf.setFillColorRGB(*BLACK) # RGB values for black
|
150 |
-
|
151 |
-
# Draw the string
|
152 |
pdf.drawString(table_x + BOX_PADDING, current_y + 6, title)
|
153 |
-
|
154 |
current_y -= LINE_HEIGHT
|
155 |
pdf.setFont("Helvetica", STANDARD_FONT_SIZE)
|
156 |
|
157 |
def format_currency(value):
|
158 |
-
# Format with 2 decimal places and thousands separator
|
159 |
return "{:,.2f}".format(value).replace(",", " ")
|
160 |
|
161 |
-
|
162 |
def draw_item_row(item, indent=False):
|
163 |
nonlocal current_y
|
164 |
pdf.setFillColorRGB(*BLACK)
|
@@ -172,16 +159,16 @@ class InvoiceService:
|
|
172 |
(str(item.quantity), 50),
|
173 |
(f"{item.length:.2f}", 60),
|
174 |
(f"{format_currency(item.unit_price)}", 60),
|
175 |
-
(f"{format_currency(item.total_price)} DH", 170)
|
176 |
]
|
177 |
|
178 |
for i, (value, width) in enumerate(cells):
|
179 |
draw_box(pdf, current_x, current_y, width, LINE_HEIGHT)
|
180 |
|
181 |
-
if i == len(cells) - 1:
|
182 |
-
pdf.setFont("Helvetica-Bold", STANDARD_FONT_SIZE)
|
183 |
else:
|
184 |
-
pdf.setFont("Helvetica", STANDARD_FONT_SIZE)
|
185 |
|
186 |
if isinstance(value, str) and value.startswith(" "):
|
187 |
pdf.drawString(current_x + 20, current_y + 6, value.strip())
|
@@ -190,9 +177,23 @@ class InvoiceService:
|
|
190 |
|
191 |
current_x += width
|
192 |
|
193 |
-
pdf.setFont("Helvetica", STANDARD_FONT_SIZE)
|
194 |
current_y -= LINE_HEIGHT
|
195 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
196 |
|
197 |
# Draw sections
|
198 |
sections = [
|
@@ -203,8 +204,6 @@ class InvoiceService:
|
|
203 |
]
|
204 |
|
205 |
for section_title, keyword in sections:
|
206 |
-
print("-------------")
|
207 |
-
print(f"section_title: {section_title}")
|
208 |
draw_section_header2(section_title)
|
209 |
items = [i for i in data.items if keyword in i.description]
|
210 |
for item in items:
|
@@ -217,37 +216,32 @@ class InvoiceService:
|
|
217 |
pdf.rect(20, current_y - nb_box_height, nb_box_width, nb_box_height, stroke=1)
|
218 |
pdf.setFont("Helvetica-Bold", STANDARD_FONT_SIZE)
|
219 |
pdf.drawString(30, current_y - nb_box_height + 60, "NB:")
|
220 |
-
|
221 |
# Add the new text
|
222 |
pdf.setFont("Helvetica", STANDARD_FONT_SIZE)
|
223 |
nb_text = "Toute modification apportée aux plans BA initialement fournis, entraine automatiquement la modification de ce devis."
|
224 |
-
# Split text to fit in box
|
225 |
words = nb_text.split()
|
226 |
lines = []
|
227 |
current_line = []
|
228 |
-
|
229 |
for word in words:
|
230 |
current_line.append(word)
|
231 |
-
# Check if current line width exceeds box width
|
232 |
if pdf.stringWidth(' '.join(current_line), "Helvetica", STANDARD_FONT_SIZE) > nb_box_width - 20:
|
233 |
-
current_line.pop()
|
234 |
lines.append(' '.join(current_line))
|
235 |
current_line = [word]
|
236 |
-
|
237 |
if current_line:
|
238 |
lines.append(' '.join(current_line))
|
239 |
-
|
240 |
-
# Draw each line
|
241 |
for i, line in enumerate(lines):
|
242 |
pdf.drawString(30, current_y - nb_box_height + 45 - (i * 10), line)
|
243 |
|
244 |
# ADD text after the NB box
|
245 |
pdf.setFont("Helvetica-Bold", 9)
|
246 |
-
pdf.drawString(30
|
247 |
-
|
248 |
-
|
249 |
# Totals section
|
250 |
-
# set font to bold
|
251 |
pdf.setFont("Helvetica-Bold", 12)
|
252 |
current_y -= 20
|
253 |
totals_table_width = 300
|
@@ -270,33 +264,27 @@ class InvoiceService:
|
|
270 |
footer_text = "Douar Ait Laarassi Tidili, Cercle El Kelâa, Route de Safi, Km 14-40000 Marrakech"
|
271 |
pdf.drawCentredString(page_width / 2 + 20, 30, footer_text)
|
272 |
|
273 |
-
#
|
274 |
-
# i have salah with 0666666666 and khaled with 077777777 and ismale with 08888888 and jamal with 099999999
|
275 |
commercial_info = dict(
|
276 |
-
salah
|
277 |
-
khaled=
|
278 |
-
ismail=
|
279 |
-
jamal
|
280 |
)
|
281 |
-
|
282 |
-
print(f"Commercial value: {data.commercial}") # Add this debug line
|
283 |
if data.commercial and data.commercial.lower() != 'divers':
|
284 |
commercial_text = f"Commercial: {data.commercial.upper()}"
|
285 |
commercial_phone = f"Tél: {commercial_info.get(data.commercial.lower(), '')}"
|
286 |
-
|
287 |
pdf.drawString(MARGIN + 10, 30, commercial_text)
|
288 |
-
#draw under the commercial text :
|
289 |
pdf.drawString(MARGIN + 10, 20, commercial_phone)
|
290 |
footer_contact = "Tél: 05 24 01 55 54 Fax : 05 24 01 55 29 E-mail : [email protected]"
|
291 |
pdf.drawCentredString(page_width / 2 + 10, 20, footer_contact)
|
292 |
-
## add the time and page number
|
293 |
-
|
294 |
pdf.drawString(page_width - 100, 30, f"Date: {datetime.datetime.now().strftime('%d/%m/%Y')}")
|
295 |
pdf.drawString(page_width - 100, 20, f"Page {pdf.getPageNumber()}/{pdf.getPageNumber()}")
|
296 |
else:
|
297 |
footer_contact = "Tél: 05 24 01 55 54 Fax : 05 24 01 55 29 E-mail : [email protected]"
|
298 |
pdf.drawCentredString(page_width / 2 + 10, 20, footer_contact)
|
299 |
-
pdf.drawString(page_width - 100, 30, f"Date: {datetime.datetime().
|
300 |
pdf.drawString(page_width - 100, 20, f"Page {pdf.getPageNumber()}/{pdf.getPageNumber()}")
|
301 |
|
302 |
pdf.save()
|
|
|
27 |
MARGIN = 30
|
28 |
LINE_HEIGHT = 20
|
29 |
BOX_PADDING = 10
|
30 |
+
STANDARD_FONT_SIZE = 10
|
31 |
+
BOTTOM_MARGIN = 50 # Minimum margin at the bottom of the page
|
32 |
|
33 |
# Helper function to draw centered text
|
34 |
def draw_centered_text(pdf, text, x, y, width, font="Helvetica", size=STANDARD_FONT_SIZE):
|
|
|
55 |
pdf.drawImage(logo_path, MARGIN, top_margin + 30, width=100, height=60)
|
56 |
|
57 |
# Right side: DEVIS and Client Box - moved far right and up
|
58 |
+
pdf.setFont("Helvetica-Bold", 36)
|
59 |
devis_text = "DEVIS"
|
60 |
devis_width = pdf.stringWidth(devis_text, "Helvetica-Bold", 36)
|
61 |
devis_x = page_width - devis_width - MARGIN - 10
|
|
|
123 |
table_x = (page_width - total_width) / 2 # Center table
|
124 |
draw_box(pdf, table_x, table_y, total_width, LINE_HEIGHT, fill_color=HEADER_BLUE)
|
125 |
pdf.setFillColorRGB(*WHITE)
|
126 |
+
|
127 |
+
current_x = table_x
|
|
|
|
|
128 |
for title, width in headers:
|
129 |
draw_box(pdf, current_x, table_y, width, LINE_HEIGHT)
|
130 |
pdf.setFillColorRGB(*WHITE)
|
131 |
draw_centered_text(pdf, title, current_x, table_y + 6, width)
|
132 |
current_x += width
|
133 |
|
|
|
134 |
# Draw sections and items
|
135 |
current_y = table_y - LINE_HEIGHT - 10
|
136 |
|
|
|
|
|
137 |
def draw_section_header2(title):
|
138 |
nonlocal current_y
|
139 |
draw_box(pdf, table_x, current_y, total_width, LINE_HEIGHT, fill_color=WHITE)
|
140 |
+
pdf.setFont("Helvetica-Bold", 9)
|
141 |
+
pdf.setFillColorRGB(*BLACK)
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
pdf.drawString(table_x + BOX_PADDING, current_y + 6, title)
|
|
|
143 |
current_y -= LINE_HEIGHT
|
144 |
pdf.setFont("Helvetica", STANDARD_FONT_SIZE)
|
145 |
|
146 |
def format_currency(value):
|
|
|
147 |
return "{:,.2f}".format(value).replace(",", " ")
|
148 |
|
|
|
149 |
def draw_item_row(item, indent=False):
|
150 |
nonlocal current_y
|
151 |
pdf.setFillColorRGB(*BLACK)
|
|
|
159 |
(str(item.quantity), 50),
|
160 |
(f"{item.length:.2f}", 60),
|
161 |
(f"{format_currency(item.unit_price)}", 60),
|
162 |
+
(f"{format_currency(item.total_price)} DH", 170)
|
163 |
]
|
164 |
|
165 |
for i, (value, width) in enumerate(cells):
|
166 |
draw_box(pdf, current_x, current_y, width, LINE_HEIGHT)
|
167 |
|
168 |
+
if i == len(cells) - 1:
|
169 |
+
pdf.setFont("Helvetica-Bold", STANDARD_FONT_SIZE)
|
170 |
else:
|
171 |
+
pdf.setFont("Helvetica", STANDARD_FONT_SIZE)
|
172 |
|
173 |
if isinstance(value, str) and value.startswith(" "):
|
174 |
pdf.drawString(current_x + 20, current_y + 6, value.strip())
|
|
|
177 |
|
178 |
current_x += width
|
179 |
|
180 |
+
pdf.setFont("Helvetica", STANDARD_FONT_SIZE)
|
181 |
current_y -= LINE_HEIGHT
|
182 |
|
183 |
+
# Check if we need a new page
|
184 |
+
if current_y < BOTTOM_MARGIN:
|
185 |
+
pdf.showPage()
|
186 |
+
current_y = page_height - MARGIN
|
187 |
+
# Redraw headers on the new page
|
188 |
+
draw_box(pdf, table_x, current_y, total_width, LINE_HEIGHT, fill_color=HEADER_BLUE)
|
189 |
+
pdf.setFillColorRGB(*WHITE)
|
190 |
+
current_x = table_x
|
191 |
+
for title, width in headers:
|
192 |
+
draw_box(pdf, current_x, current_y, width, LINE_HEIGHT)
|
193 |
+
pdf.setFillColorRGB(*WHITE)
|
194 |
+
draw_centered_text(pdf, title, current_x, current_y + 6, width)
|
195 |
+
current_x += width
|
196 |
+
current_y -= LINE_HEIGHT
|
197 |
|
198 |
# Draw sections
|
199 |
sections = [
|
|
|
204 |
]
|
205 |
|
206 |
for section_title, keyword in sections:
|
|
|
|
|
207 |
draw_section_header2(section_title)
|
208 |
items = [i for i in data.items if keyword in i.description]
|
209 |
for item in items:
|
|
|
216 |
pdf.rect(20, current_y - nb_box_height, nb_box_width, nb_box_height, stroke=1)
|
217 |
pdf.setFont("Helvetica-Bold", STANDARD_FONT_SIZE)
|
218 |
pdf.drawString(30, current_y - nb_box_height + 60, "NB:")
|
219 |
+
|
220 |
# Add the new text
|
221 |
pdf.setFont("Helvetica", STANDARD_FONT_SIZE)
|
222 |
nb_text = "Toute modification apportée aux plans BA initialement fournis, entraine automatiquement la modification de ce devis."
|
|
|
223 |
words = nb_text.split()
|
224 |
lines = []
|
225 |
current_line = []
|
226 |
+
|
227 |
for word in words:
|
228 |
current_line.append(word)
|
|
|
229 |
if pdf.stringWidth(' '.join(current_line), "Helvetica", STANDARD_FONT_SIZE) > nb_box_width - 20:
|
230 |
+
current_line.pop()
|
231 |
lines.append(' '.join(current_line))
|
232 |
current_line = [word]
|
233 |
+
|
234 |
if current_line:
|
235 |
lines.append(' '.join(current_line))
|
236 |
+
|
|
|
237 |
for i, line in enumerate(lines):
|
238 |
pdf.drawString(30, current_y - nb_box_height + 45 - (i * 10), line)
|
239 |
|
240 |
# ADD text after the NB box
|
241 |
pdf.setFont("Helvetica-Bold", 9)
|
242 |
+
pdf.drawString(30, current_y - nb_box_height - 15, "Validité du devis : 1 mois")
|
243 |
+
|
|
|
244 |
# Totals section
|
|
|
245 |
pdf.setFont("Helvetica-Bold", 12)
|
246 |
current_y -= 20
|
247 |
totals_table_width = 300
|
|
|
264 |
footer_text = "Douar Ait Laarassi Tidili, Cercle El Kelâa, Route de Safi, Km 14-40000 Marrakech"
|
265 |
pdf.drawCentredString(page_width / 2 + 20, 30, footer_text)
|
266 |
|
267 |
+
# Add commercial info to footer
|
|
|
268 |
commercial_info = dict(
|
269 |
+
salah="06 62 29 99 78",
|
270 |
+
khaled="06 66 24 80 94",
|
271 |
+
ismail="06 66 24 50 15",
|
272 |
+
jamal="06 70 08 36 50"
|
273 |
)
|
274 |
+
|
|
|
275 |
if data.commercial and data.commercial.lower() != 'divers':
|
276 |
commercial_text = f"Commercial: {data.commercial.upper()}"
|
277 |
commercial_phone = f"Tél: {commercial_info.get(data.commercial.lower(), '')}"
|
|
|
278 |
pdf.drawString(MARGIN + 10, 30, commercial_text)
|
|
|
279 |
pdf.drawString(MARGIN + 10, 20, commercial_phone)
|
280 |
footer_contact = "Tél: 05 24 01 55 54 Fax : 05 24 01 55 29 E-mail : [email protected]"
|
281 |
pdf.drawCentredString(page_width / 2 + 10, 20, footer_contact)
|
|
|
|
|
282 |
pdf.drawString(page_width - 100, 30, f"Date: {datetime.datetime.now().strftime('%d/%m/%Y')}")
|
283 |
pdf.drawString(page_width - 100, 20, f"Page {pdf.getPageNumber()}/{pdf.getPageNumber()}")
|
284 |
else:
|
285 |
footer_contact = "Tél: 05 24 01 55 54 Fax : 05 24 01 55 29 E-mail : [email protected]"
|
286 |
pdf.drawCentredString(page_width / 2 + 10, 20, footer_contact)
|
287 |
+
pdf.drawString(page_width - 100, 30, f"Date: {datetime.datetime.now().strftime('%d/%m/%Y')}")
|
288 |
pdf.drawString(page_width - 100, 20, f"Page {pdf.getPageNumber()}/{pdf.getPageNumber()}")
|
289 |
|
290 |
pdf.save()
|
sql_app.db
CHANGED
Binary files a/sql_app.db and b/sql_app.db differ
|
|