BonPrinter v1.2.0
Thermal Printer tool
Loading...
Searching...
No Matches
report.py
Go to the documentation of this file.
1"""!
2********************************************************************************
3@file report.py
4@brief read and write print log data and create report
5********************************************************************************
6"""
7
8import os
9import csv
10import shutil
11import logging
12import copy
13from typing import NamedTuple, Optional, Any, TYPE_CHECKING
14from datetime import datetime
15
16from Source.version import __title__
17from Source.Util.app_data import open_explorer, EUser, I_GROUP_1, I_GROUP_2, I_GROUP_3, S_UNIT, ItemNumber, \
18 read_sales, write_sales, L_CONFIG_USER, I_NUMBER_OF_SALES, F_DEFAULT_SALES, I_ITEM_ARRAY_SIZE, \
19 S_DATE_PRINT_FORMAT, L_PRINT_FILE_HEADER, L_GROUP, L_DEVICE, S_UNIT_SYMBOL
20
21from Source.Model.config import S_ITEM_FILE, write_config_to_file # pylint: disable=wrong-import-position
22from Source.Model.language import L_COMBINED_TITLE, L_REPORT_TITLE, L_REPORT_STATUS_TITLE, L_USER_STATUS_TITLE # pylint: disable=wrong-import-position
23if TYPE_CHECKING:
24 from Source.Controller.main_window import MainWindow
25
26log = logging.getLogger(__title__)
27
28B_POWERSHELL_COPY = False
29
30S_PRINT_LOG_NAME = "PrintLog"
31
32S_PRINT_FILE = f"{S_PRINT_LOG_NAME}.csv"
33
34S_REPORT_NAME = "Report"
35S_REPORT_FOLDER_NAME = "PrintReport"
36S_COMBINE_FOLDER_NAME = "CombinedReport"
37
38
39SALES_KEY = "sales"
40SUM_KEY = "sum"
41
42D_GROUP_SUMMARY: dict[int, dict[str, Any]] = {I_GROUP_1: {SUM_KEY: 0, SALES_KEY: {}},
43 I_GROUP_2: {SUM_KEY: 0, SALES_KEY: {}},
44 I_GROUP_3: {SUM_KEY: 0, SALES_KEY: {}}}
45
46
47class Item(NamedTuple):
48 """!
49 @brief properties of printed article
50 """
51 amount: int
52 name: str
53 pos: int
54 group: int
55 price: str
56 price_total: str
57 user: str
58 date: str
59 port: bool # TODO port in print umbenennen
60
61
62class ItemReport(NamedTuple):
63 """!
64 @brief properties of logged article
65 """
66 amount: int
67 name: str
68 pos: int
69 group: int
70 price: float
71 price_total: float
72 user: str
73 date: datetime
74 port: str
75
76
77def create_item(count: int, name: str, pos: int, group: int, price: float, price_total: float, user: str | None, date: datetime, port: bool) -> Item:
78 """!
79 @brief Create Item to write to log
80 @param count : number of items
81 @param name : name of item
82 @param pos : article position
83 @param group : group number
84 @param price : single article prince
85 @param price_total : total article price
86 @param user : user name who print the article
87 @param date : datetime of item print
88 @param port : status if item print or only save (if print use COM port name)
89 @return item
90 """
91 s_datetime = date.strftime(S_DATE_PRINT_FORMAT) if (date is not None) else None
92 assert (user is not None), "None user will print"
93 return Item(count, name, pos, group, f"{price:.2f}", f"{price_total:.2f}", user, s_datetime, port)
94
95
96class Report:
97 """!
98 @brief Report class to log and create sales articles
99 @param ui : main window object
100 """
101
102 def __init__(self, ui: "MainWindow") -> None:
103 self.ui = ui
104 self.l_bar_items = [0] * I_ITEM_ARRAY_SIZE
105 self.l_marked_items = [False] * I_ITEM_ARRAY_SIZE
106 for i in range(ItemNumber.MAX):
107 self.l_marked_items[i] = self.ui.model.c_config.get_item_mark_status(i + 1) # set default mark status
108 self.l_table_position: list[int | None] = [None] * I_ITEM_ARRAY_SIZE # table position of items to delete single selected items
109 self.s_report_text: str = ""
110 self.s_report_user: str = ""
111 self.s_total_sum: str = ""
112 self.out_folder: str | None = None
113 self.b_log_modified: bool = False
114 self.l_group_values: list[list[float]] = []
115 self.d_printed_articles: dict[str, int] = {}
116 self.d_combine_data: dict[str, float] = {}
117 self.b_log_valid_verified = True # help variable to get info out of function
118
119 # sales
120 self.l_sales = read_sales()
121
122 self.clear_print_items()
123
124 def check_setting_sales_exist(self) -> bool:
125 """!
126 @brief Write printed articles to log file
127 @return [True] sales exist; [False] sales not exist
128 """
129 b_sales_exist = False
130 for s_sale in self.l_sales:
131 if s_sale != 0:
132 b_sales_exist = True
133 return b_sales_exist
134
135 def check_sales_exist(self) -> bool:
136 """!
137 @brief Check if sales in log file or settings exist
138 @return [True] sales exist; [False] sales not exist
139 """
140 if self.ui.model.c_auth.b_login_state:
141 b_edit_user = self.ui.model.c_auth.check_user_login(L_CONFIG_USER)
142 if not b_edit_user:
143 user = self.ui.model.c_auth.s_login_user
144 if user is not None:
145 i_user_pos = self.ui.model.c_auth.get_user_position()
146 if i_user_pos is None:
147 b_sales_exist = False
148 else:
149 b_sales_exist = bool(self.l_sales[i_user_pos] != 0) # TODO user mit 0 Euro prints b_sales_exist = True
150 else:
151 b_sales_exist = False
152 else:
153 b_sales_exist = self.check_setting_sales_exist() or os.path.exists(S_PRINT_FILE)
154 else:
155 b_sales_exist = False
156 return b_sales_exist
157
158 def clear_print_items(self) -> None:
159 """!
160 @brief clear items to hold in storage before print out
161 """
162 self.l_bar_items = [0] * I_ITEM_ARRAY_SIZE
163 self.l_table_position = [None] * I_ITEM_ARRAY_SIZE
164
165 def write_data_to_print_file(self, l_items: list[Item], i_user_pos: int, f_price: float, s_com_port: str | None) -> None:
166 """!
167 @brief Write printed articles to log file
168 @param l_items : list of printed articles
169 @param i_user_pos : user (position) that print this items
170 @param f_price : price of printed items
171 @param s_com_port : COM port (write to print log without printer is possible)
172 """
173 b_item = True
174 if b_item: # do not safe EC sales for reference
175 self.l_sales[i_user_pos] += f_price
176 write_sales(i_user_pos, self.l_sales[i_user_pos])
177
178 if s_com_port is None:
179 s_com_port = "None" # to save print information
180 l_list = []
181 for item in l_items:
182 b_print = False
183 if item.port or b_print: # if print out
184 port = s_com_port
185 else:
186 port = "None"
187 l_list.append([item.amount, item.name, item.pos, item.group, item.price_total, item.user, item.date, port])
188 if not os.path.exists(S_PRINT_FILE):
189 self.write_data_to_file([L_PRINT_FILE_HEADER])
190 self.write_data_to_file(l_list)
191
192 def create_report(self, l_files: Optional[list[str]] = None, b_combine: bool = False, b_clear_report: bool = False, d_item: Optional[dict[str, dict[str, str]]] = None,
193 b_startup_check: bool = False, s_path: Optional[str] = None, b_open_folder: bool = False) -> None:
194 """!
195 @brief Create report or show only status report
196 @param l_files : list of files to create report
197 @param b_combine : [True] create combined report; [False] create single report
198 @param b_clear_report : [True] create report and clear log; [False] show only status of printed articles
199 @param d_item : item configuration data
200 @param b_startup_check : [True] only check reports, do not open; [False] open file
201 @param s_path : path to write report
202 @param b_open_folder : open output folder in explorer status
203 """
204 now = datetime.now()
205 if b_combine:
206 s_folder_name = S_COMBINE_FOLDER_NAME
207 self.ui.set_status(["Combine report", "Bericht wird kombiniert"])
208 else:
209 s_folder_name = S_REPORT_FOLDER_NAME
210 if not b_startup_check:
211 self.ui.set_status(["Create report", "Bericht wird erstellt"])
212
213 # collect all CSV files for report
214 l_fix_data: list[list[list[str]] | None] | None = None
215 if l_files is None:
216 self.d_combine_data = {} # clear data for combined report
217 l_files = [S_PRINT_FILE]
218 elif b_combine and not b_startup_check:
219 self.d_combine_data = {} # clear data for combined report
220 for report_file in sorted(l_files): # sort files for combine
221 self.create_report(l_files=[report_file], b_combine=b_combine, b_clear_report=False, b_startup_check=True)
222
223 b_log_data_readable, l_item, l_header = self.combine_files(l_files, l_fix_data=l_fix_data)
224
225 if b_combine or b_clear_report:
226 user = None
227 else:
228 if self.ui.model.c_auth.s_login_user in L_CONFIG_USER:
229 user = None
230 else:
231 user = self.ui.model.c_auth.s_login_user
232 self.s_report_user = user if (user is not None) else ""
233
234 # get report text
235 s_text = self.get_report_text(l_files, l_item, now, b_combine=b_combine, b_clear_report=b_clear_report, user_report=user)
236
237 self.s_report_text = s_text # save to global to print out
238
239 if not b_log_data_readable:
240 self.ui.set_status(["Print Log file not readable", "Protokolldatei nicht lesbar"], True)
241 elif not self.b_log_valid_verified:
242 self.ui.set_status(["Print Log data not verified", "Protokolldatei nicht verifiziert"], True)
243 self.b_log_modified = True
244 else:
245 self.b_log_modified = False
246
247 if (b_clear_report or b_combine) and not b_startup_check:
248 device_name = self.ui.model.c_config.get_device_name()
249 device_suffix = f"_{device_name}" if (not b_combine and (device_name != "")) else ""
250 suffix = now.strftime("%Y-%m-%d_%Hh%Mm%Ss") + device_suffix
251
252 s_folder_name = S_COMBINE_FOLDER_NAME if b_combine else S_REPORT_FOLDER_NAME
253 if s_path is not None:
254 output_path = s_path
255 else:
256 output_path = self.ui.model.s_output_path
257 s_folder = f"{output_path}/{s_folder_name}_{suffix}"
258 log.debug("Create folder: %s", s_folder)
259
260 # create report folder if not exist
261 if not os.path.isdir(s_folder):
262 os.makedirs(s_folder)
263
264 # create summary report (MarkDown)
265 md_report_path = f"{s_folder}/{S_REPORT_NAME}_{suffix}.md"
266 log.debug("Open file: %s", md_report_path)
267 with open(md_report_path, mode="w", encoding="utf-8") as file:
268 file.write(s_text)
269
270 if b_combine or os.path.exists(S_PRINT_FILE):
271 xl_report_path = None
272
273 if not b_combine:
274 csv_print_log_report_path = os.path.join(s_folder, f"{S_PRINT_LOG_NAME}_{suffix}.csv")
275
276 # copy print log (CSV) to report folder
277 shutil.copy(S_PRINT_FILE, csv_print_log_report_path)
278
279 # copy articles configuration to report
280 if d_item is not None:
281 article_configuration_path = f"{s_folder}/{S_ITEM_FILE}"
282 write_config_to_file(article_configuration_path, d_item)
283 else:
284 article_configuration_path = None
285
286 # remove print log (CSV) to clean data for next report
287 self.clear_setting_log() # TODO wird nur aufgerufen wenn CSV existiert
288 os.remove(S_PRINT_FILE)
289
290 if B_POWERSHELL_COPY:
291 # copy folder content to clipboard
292 command = f"powershell Set-Clipboard -LiteralPath {s_folder}"
293 os.system(command)
294
295 # save path to open out folder
296 self.out_folder = s_folder
297 if b_open_folder:
298 open_explorer(s_folder)
299 else:
300 self.out_folder = None
301 self.ui.set_status([f"Folder already exist: {s_folder}",
302 f"Ordner existiert bereits: {s_folder}"], True)
303 else: # show only status
304 self.out_folder = None
305
306 def group_items(self, d_dict: dict[int, dict[str, Any]], b_print_values: bool = True, b_total_values: bool = False) -> str:
307 """!
308 @brief Create summary report text of printed articles.
309 @param d_dict : dictionary with printed articles
310 @param b_print_values : status if print values (not only articles)
311 @param b_total_values : status if group total items to save in global data
312 @return return sorted group text
313 """
314 s_text = ""
315 if b_total_values:
316 self.l_group_values = []
317 for group in [I_GROUP_1, I_GROUP_2, I_GROUP_3]:
318 f_sum = d_dict[group][SUM_KEY]
319 if (f_sum != 0) or d_dict[group][SALES_KEY]:
320 s_text += f"* {self.ui.model.c_language.get_language_text(L_GROUP)} {group}:"
321 if b_print_values:
322 s_text += f" {f_sum:.2f} {S_UNIT} \n"
323 else:
324 s_text += "\n"
325 tax = self.ui.model.c_config.get_group_tax(group)
326 if b_total_values:
327 self.l_group_values.append([f_sum, tax])
328 for item, count in sorted(d_dict[group][SALES_KEY].items()):
329 s_text += f" {count} x {item} \n"
330 s_text += "\n"
331 return s_text
332
333 def get_report_text(self, l_files: list[str], l_data: list[ItemReport], time: datetime, b_combine: bool = False, b_clear_report: bool = False, user_report: Optional[str] = None) -> str:
334 """!
335 @brief Create summary report text of printed articles.
336 @param l_files : file to save total data for combined report
337 @param l_data : list of printed articles
338 @param time : actual time stamp for report
339 @param b_combine : [True] create combined report; [False] create single report
340 @param b_clear_report : [True] create report text and clear printed sales in settings; [False] create status text
341 @param user_report : create report for this user; None: create for all user
342 @return return report text
343 """
344 d_user_item = {}
345 d_total_items = copy.deepcopy(D_GROUP_SUMMARY)
346 d_free_items = copy.deepcopy(D_GROUP_SUMMARY)
347
348 f_ec_sum = 0.0
349
350 if b_combine:
351 s_title = self.ui.model.c_language.get_language_text(L_COMBINED_TITLE)
352 else:
353 if b_clear_report:
354 s_title = self.ui.model.c_language.get_language_text(L_REPORT_TITLE)
355 else:
356 if user_report is None:
357 s_title = self.ui.model.c_language.get_language_text(L_REPORT_STATUS_TITLE)
358 else:
359 s_title = self.ui.model.c_language.get_language_text(L_USER_STATUS_TITLE)
360
361 s_report = f"# {s_title}\n\n"
362
363 if l_data:
364 min_date = None
365 max_date = None
366 # sort articles by user and group
367 for entry in l_data:
368 name = entry.name
369 count = entry.amount
370 price = entry.price_total
371 user = entry.user
372 date = entry.date
373 printer = entry.port
374 b_item = True
375 if b_item:
376 if (user_report is None) or (user_report == user):
377 if min_date is None:
378 min_date = date
379 else:
380 min_date = min(min_date, date)
381 if max_date is None:
382 max_date = date
383 else:
384 max_date = max(max_date, date)
385 if entry.group not in [I_GROUP_1, I_GROUP_2]:
386 group = I_GROUP_3 # all other groups are group 3
387 else:
388 group = entry.group
389
390 if user != EUser.FREE:
391 if user not in d_user_item:
392 d_user_item[user] = copy.deepcopy(D_GROUP_SUMMARY)
393
394 # user sum
395 if name in d_user_item[user][group][SALES_KEY]:
396 d_user_item[user][group][SALES_KEY][name] += count
397 else:
398 d_user_item[user][group][SALES_KEY][name] = count
399 d_user_item[user][group][SUM_KEY] += price
400
401 # total sum
402 if name in d_total_items[group][SALES_KEY]:
403 d_total_items[group][SALES_KEY][name] += count
404 else:
405 d_total_items[group][SALES_KEY][name] = count
406 d_total_items[group][SUM_KEY] += price
407 else:
408 if name in d_free_items[group][SALES_KEY]:
409 d_free_items[group][SALES_KEY][name] += count
410 else:
411 d_free_items[group][SALES_KEY][name] = count
412 else:
413 f_ec_sum += price
414 # date
415 s_create_time = time.strftime(S_DATE_PRINT_FORMAT)
416 device_name = self.ui.model.c_config.get_device_name()
417 if not b_combine and (device_name != ""):
418 device_lable = self.ui.model.c_language.get_language_text(L_DEVICE)
419 s_report += f"{device_lable}:\t{device_name} \n"
420 if len(l_files) > 1:
421 for file in l_files[1:]: # ignore first local file
422 s_report += f"{device_lable}:\t{file} \n"
423 s_report += f"{self.ui.model.c_language.get_language_text(['Create', 'Erst.'])}:\t{s_create_time} \n"
424 s_report += f"{self.ui.model.c_language.get_language_text(['From', 'Von'])}: \t{str(min_date).replace('-', '/')} \n"
425 s_report += f"{self.ui.model.c_language.get_language_text(['To', 'Bis'])}: \t{str(max_date).replace('-', '/')} \n\n"
426
427 # Total sales
428 if user_report is None:
429 s_report += f"## {self.ui.model.c_language.get_language_text(['Total prints', 'Gesamte Ausdrucke'])}\n\n"
430 total_sum = d_total_items[I_GROUP_1][SUM_KEY] + d_total_items[I_GROUP_2][SUM_KEY] + d_total_items[I_GROUP_3][SUM_KEY]
431 if (not b_combine) and (len(l_files) == 1):
432 s_file_name = os.path.basename(l_files[0])
433 prefix = f"{S_PRINT_LOG_NAME}_"
434 suffix = ".csv"
435 if s_file_name.startswith(prefix):
436 s_file_name = s_file_name[len(prefix):]
437 if s_file_name.endswith(suffix):
438 s_file_name = s_file_name[:-len(suffix)]
439 self.d_combine_data[s_file_name] = total_sum
440 s_total_sum = f"{total_sum:.2f}"
441 if user_report is None:
442 self.s_total_sum = s_total_sum
443 s_report += f"{self.ui.model.c_language.get_language_text(['Total sum', 'Gesamtsumme'])}: {s_total_sum} {S_UNIT}\n"
444 if f_ec_sum != 0:
445 s_report += f"{self.ui.model.c_language.get_language_text(['inc. EC', 'ink. EC'])}: {f_ec_sum:.2f} {S_UNIT}\n"
446 s_report += "\n"
447 s_total_text = self.group_items(d_total_items, b_total_values=True)
448 if len(d_user_item) > 1: # print total items only if multiple user prints present (prevent double item report)
449 s_report += s_total_text
450
451 # User sales
452 if user_report is None:
453 s_report += f"## {self.ui.model.c_language.get_language_text(['Prints per user', 'Ausdrucke pro Benutzer'])}\n\n"
454 b_log_invalid = False
455 l_checked_user = [False] * I_NUMBER_OF_SALES
456 if d_user_item:
457 for user in sorted(d_user_item.keys()):
458 if (user_report is None) or (user_report == user):
459 user_sales_sum = d_user_item[user][I_GROUP_1][SUM_KEY]\
460 + d_user_item[user][I_GROUP_2][SUM_KEY]\
461 + d_user_item[user][I_GROUP_3][SUM_KEY]
462 s_price = f"{user_sales_sum:.2f}"
463 if user_report is not None:
464 self.s_total_sum = s_price
465 s_report += f"### {user}\n"
466 s_report += f"{self.ui.model.c_language.get_language_text(['Sum', 'Summe'])}: {s_price} {S_UNIT} \n"
467 user_tax = self.ui.model.c_config.get_user_tax()
468 if (not user.startswith(EUser.LOCAL.value)) and (user_tax != 0): # do not use check_user_login because local is modifies in combined report with prefix
469 involv = user_sales_sum * (user_tax / 100)
470 to_pay = user_sales_sum - involv
471 s_report += f"{self.ui.model.c_language.get_language_text(['Involv', 'Beteiligung'])}: {involv:.2f} {S_UNIT} ({user_tax} %) \n"
472 s_report += f"{self.ui.model.c_language.get_language_text(['To Pay', 'zu zahlen'])}: {to_pay:.2f} {S_UNIT} \n"
473 if (not b_combine) and (len(l_files) == 1):
474 i_user_pos = self.ui.model.c_auth.get_user_position(user)
475 if i_user_pos is not None:
476 if i_user_pos < I_NUMBER_OF_SALES:
477 l_checked_user[i_user_pos] = True
478 s_price_ref = f"{self.l_sales[i_user_pos]:.2f}"
479 if s_price_ref != s_price:
480 b_log_invalid = True
481 s_ref_price = f"{self.l_sales[i_user_pos]:.2f}"
482 s_report += f" {self.ui.model.c_language.get_language_text(['Log modified! valid', 'Modifiziert! Gültig'])}: {s_ref_price} {S_UNIT} \n"
483 s_report += self.group_items(d_user_item[user])
484
485 b_missing_sales = False
486 if not b_combine and (user_report is None):
487 # check if vales missing in log
488 for i_pos, f_user_sales in enumerate(self.l_sales):
489 if not l_checked_user[i_pos]:
490 if f_user_sales != 0:
491 b_missing_sales = True
492 user = self.ui.model.c_auth.get_user_name_from_position(i_pos)
493 s_price = f"{0:.2f}" # price = 0.00
494 s_ref_price = f"{f_user_sales:.2f}"
495 s_report += f"* {user}: {s_price} {S_UNIT}\n"
496 s_report += f" {self.ui.model.c_language.get_language_text(['Log modified! valid', 'Modifiziert! Gültig'])}: {s_ref_price} {S_UNIT} \n"
497
498 if user_report is None:
499 if d_free_items[I_GROUP_1][SALES_KEY] or d_free_items[I_GROUP_2][SALES_KEY] or d_free_items[I_GROUP_3][SALES_KEY]:
500 s_report += f"## {self.ui.model.c_language.get_language_text(['Free Articles', 'Freie Artikel'])}\n\n"
501 s_report += self.group_items(d_free_items, b_print_values=False)
502 self.d_printed_articles = d_total_items[I_GROUP_1][SALES_KEY] | d_total_items[I_GROUP_2][SALES_KEY] | d_total_items[I_GROUP_3][SALES_KEY]
503
504 if b_combine:
505 self.b_log_valid_verified = True
506 else:
507 self.b_log_valid_verified = not b_missing_sales and not b_log_invalid # save to global to get info out of this function
508 return s_report
509
510 def clear_setting_log(self) -> None:
511 """!
512 @brief Clear sales in settings
513 """
514 for i_user in range(I_NUMBER_OF_SALES):
515 self.l_sales[i_user] = F_DEFAULT_SALES # clear reference check
516 write_sales(i_user, F_DEFAULT_SALES)
517
518 def combine_files(self, l_files: list[str], l_fix_data: Optional[list[list[list[str]] | None]] = None) -> tuple[bool, list[ItemReport], list[str]]:
519 """!
520 @brief Combine Reports to create single report from multiple other data.
521 @param l_files : list of files to combine
522 @param l_fix_data : use this fix data and do not read from file
523 @return readable status of file, items of files and header of first file
524 """
525 l_all_item: list[ItemReport] = []
526 l_header: list[str] = []
527 b_log_data_readable = False
528
529 for i, file in enumerate(l_files):
530 fix_data = None if (l_fix_data is None) else l_fix_data[i]
531 b_log_data_readable, l_item, l_header = self.get_items_from_print_file(file, bool(len(l_files) > 1), fix_data=fix_data)
532 if b_log_data_readable:
533 l_all_item += l_item
534 else:
535 break
536
537 return b_log_data_readable, l_all_item, l_header
538
539 def get_items_from_print_file(self, s_file_name: str = S_PRINT_FILE, b_combine: bool = False,
540 fix_data: Optional[list[list[str]]] = None) -> tuple[bool, list[ItemReport], list[str]]:
541 """!
542 @brief Get items from printouts from log file.
543 @param s_file_name : file to read
544 @param b_combine : status if combine reports to extend local user with device name
545 @param fix_data : use this fix data and do not read from file
546 @return return printed items
547 """
548 s_device_suffix: str | None = None
549 if b_combine:
550 s_device_suffix = os.path.basename(s_file_name)
551 suffix = ".csv"
552 if s_device_suffix.endswith(suffix):
553 s_device_suffix = s_device_suffix[:-len(suffix)]
554 l_device_suffix = s_device_suffix.split("_")
555 if len(l_device_suffix) == 4: # default file name has third position as device name
556 s_device_suffix = l_device_suffix[-1]
557 else:
558 s_device_suffix = None
559 try:
560 l_header = []
561 if fix_data is None:
562 l_raw_data = self.read_data_from_print_file(s_file_name)
563 else:
564 l_raw_data = fix_data
565 l_item = []
566 for i, row in enumerate(l_raw_data):
567 if i != 0:
568 user = row[5]
569 if (s_device_suffix is not None) and (user == EUser.LOCAL):
570 user = f"{user}_{s_device_suffix}" # rename local user for combined report with multiple devices
571 name = row[1]
572 price_total = float(row[4])
573 amount = int(row[0])
574 item = ItemReport(amount, name, int(row[2]), int(row[3]), 0.0,
575 price_total, user, datetime.strptime(row[6], S_DATE_PRINT_FORMAT), row[7])
576 l_item.append(item)
577 else:
578 l_header = row
579 except BaseException:
580 l_item = []
581 l_header = []
582 b_log_data_readable = False
583 else:
584 b_log_data_readable = True
585
586 return b_log_data_readable, l_item, l_header
587
588 def read_data_from_print_file(self, s_file_name: str = S_PRINT_FILE) -> list[list[str]]:
589 """!
590 @brief Read printouts from log file.
591 @param s_file_name : file to read
592 @return return printed items
593 """
594 l_item = []
595 if os.path.exists(s_file_name):
596 with open(s_file_name, mode="r", encoding="utf-8", newline="") as csv_file:
597 l_data = csv.reader(csv_file, delimiter=";", quotechar="|")
598 for row in l_data:
599 l_item.append(row)
600 return l_item
601
602 def write_data_to_file(self, l_items: list[list[Any]]) -> None:
603 """!
604 @brief Write printouts to log file
605 @param l_items : articles to write to log file
606 """
607 with open(S_PRINT_FILE, mode="a+", encoding="utf-8", newline="") as file:
608 writer = csv.writer(file, delimiter=";")
609 for entry in l_items:
610 writer.writerow(entry)
properties of printed article
Definition report.py:47
properties of logged article
Definition report.py:62
Report class to log and create sales articles.
Definition report.py:96
tuple[bool, list[ItemReport], list[str]] get_items_from_print_file(self, str s_file_name=S_PRINT_FILE, bool b_combine=False, Optional[list[list[str]]] fix_data=None)
Get items from printouts from log file.
Definition report.py:540
None create_report(self, Optional[list[str]] l_files=None, bool b_combine=False, bool b_clear_report=False, Optional[dict[str, dict[str, str]]] d_item=None, bool b_startup_check=False, Optional[str] s_path=None, bool b_open_folder=False)
Create report or show only status report.
Definition report.py:193
None clear_setting_log(self)
Clear sales in settings.
Definition report.py:510
str group_items(self, dict[int, dict[str, Any]] d_dict, bool b_print_values=True, bool b_total_values=False)
Create summary report text of printed articles.
Definition report.py:306
bool check_setting_sales_exist(self)
Write printed articles to log file.
Definition report.py:124
bool check_sales_exist(self)
Check if sales in log file or settings exist.
Definition report.py:135
None clear_print_items(self)
clear items to hold in storage before print out
Definition report.py:158
list[list[str]] read_data_from_print_file(self, str s_file_name=S_PRINT_FILE)
Read printouts from log file.
Definition report.py:588
None __init__(self, "MainWindow" ui)
Definition report.py:102
None write_data_to_file(self, list[list[Any]] l_items)
Write printouts to log file.
Definition report.py:602
None write_data_to_print_file(self, list[Item] l_items, int i_user_pos, float f_price, str|None s_com_port)
Write printed articles to log file.
Definition report.py:165
str get_report_text(self, list[str] l_files, list[ItemReport] l_data, datetime time, bool b_combine=False, bool b_clear_report=False, Optional[str] user_report=None)
Create summary report text of printed articles.
Definition report.py:333
tuple[bool, list[ItemReport], list[str]] combine_files(self, list[str] l_files, Optional[list[list[list[str]]|None]] l_fix_data=None)
Combine Reports to create single report from multiple other data.
Definition report.py:518
Item create_item(int count, str name, int pos, int group, float price, float price_total, str|None user, datetime date, bool port)
Create Item to write to log.
Definition report.py:77