2********************************************************************************
4@brief read and write print log data and create report
5********************************************************************************
13from typing
import NamedTuple, Optional, Any, TYPE_CHECKING
14from datetime
import datetime
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
22from Source.Model.language import L_COMBINED_TITLE, L_REPORT_TITLE, L_REPORT_STATUS_TITLE, L_USER_STATUS_TITLE
26log = logging.getLogger(__title__)
28B_POWERSHELL_COPY =
False
30S_PRINT_LOG_NAME =
"PrintLog"
32S_PRINT_FILE = f
"{S_PRINT_LOG_NAME}.csv"
34S_REPORT_NAME =
"Report"
35S_REPORT_FOLDER_NAME =
"PrintReport"
36S_COMBINE_FOLDER_NAME =
"CombinedReport"
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: {}}}
49 @brief properties of printed article
64 @brief properties of logged article
77def create_item(count: int, name: str, pos: int, group: int, price: float, price_total: float, user: str |
None, date: datetime, port: bool) -> Item:
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)
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)
98 @brief Report class to log and create sales articles
99 @param ui : main window object
106 for i
in range(ItemNumber.MAX):
126 @brief Write printed articles to log file
127 @return [True] sales exist; [False] sales not exist
129 b_sales_exist =
False
137 @brief Check if sales in log file or settings exist
138 @return [True] sales exist; [False] sales not exist
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)
143 user = self.
ui.model.c_auth.s_login_user
145 i_user_pos = self.
ui.model.c_auth.get_user_position()
146 if i_user_pos
is None:
147 b_sales_exist =
False
149 b_sales_exist = bool(self.
l_sales[i_user_pos] != 0)
151 b_sales_exist =
False
155 b_sales_exist =
False
160 @brief clear items to hold in storage before print out
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)
175 self.
l_sales[i_user_pos] += f_price
176 write_sales(i_user_pos, self.
l_sales[i_user_pos])
178 if s_com_port
is None:
183 if item.port
or b_print:
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):
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:
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
206 s_folder_name = S_COMBINE_FOLDER_NAME
207 self.
ui.set_status([
"Combine report",
"Bericht wird kombiniert"])
209 s_folder_name = S_REPORT_FOLDER_NAME
210 if not b_startup_check:
211 self.
ui.set_status([
"Create report",
"Bericht wird erstellt"])
214 l_fix_data: list[list[list[str]] |
None] |
None =
None
217 l_files = [S_PRINT_FILE]
218 elif b_combine
and not b_startup_check:
220 for report_file
in sorted(l_files):
221 self.
create_report(l_files=[report_file], b_combine=b_combine, b_clear_report=
False, b_startup_check=
True)
223 b_log_data_readable, l_item, l_header = self.
combine_files(l_files, l_fix_data=l_fix_data)
225 if b_combine
or b_clear_report:
228 if self.
ui.model.c_auth.s_login_user
in L_CONFIG_USER:
231 user = self.
ui.model.c_auth.s_login_user
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)
239 if not b_log_data_readable:
240 self.
ui.set_status([
"Print Log file not readable",
"Protokolldatei nicht lesbar"],
True)
242 self.
ui.set_status([
"Print Log data not verified",
"Protokolldatei nicht verifiziert"],
True)
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
252 s_folder_name = S_COMBINE_FOLDER_NAME
if b_combine
else S_REPORT_FOLDER_NAME
253 if s_path
is not None:
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)
261 if not os.path.isdir(s_folder):
262 os.makedirs(s_folder)
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:
270 if b_combine
or os.path.exists(S_PRINT_FILE):
271 xl_report_path =
None
274 csv_print_log_report_path = os.path.join(s_folder, f
"{S_PRINT_LOG_NAME}_{suffix}.csv")
277 shutil.copy(S_PRINT_FILE, csv_print_log_report_path)
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)
284 article_configuration_path =
None
288 os.remove(S_PRINT_FILE)
290 if B_POWERSHELL_COPY:
292 command = f
"powershell Set-Clipboard -LiteralPath {s_folder}"
298 open_explorer(s_folder)
301 self.
ui.set_status([f
"Folder already exist: {s_folder}",
302 f
"Ordner existiert bereits: {s_folder}"],
True)
306 def group_items(self, d_dict: dict[int, dict[str, Any]], b_print_values: bool =
True, b_total_values: bool =
False) -> str:
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
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}:"
322 s_text += f
" {f_sum:.2f} {S_UNIT} \n"
325 tax = self.
ui.model.c_config.get_group_tax(group)
328 for item, count
in sorted(d_dict[group][SALES_KEY].items()):
329 s_text += f
" {count} x {item} \n"
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:
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
345 d_total_items = copy.deepcopy(D_GROUP_SUMMARY)
346 d_free_items = copy.deepcopy(D_GROUP_SUMMARY)
351 s_title = self.
ui.model.c_language.get_language_text(L_COMBINED_TITLE)
354 s_title = self.
ui.model.c_language.get_language_text(L_REPORT_TITLE)
356 if user_report
is None:
357 s_title = self.
ui.model.c_language.get_language_text(L_REPORT_STATUS_TITLE)
359 s_title = self.
ui.model.c_language.get_language_text(L_USER_STATUS_TITLE)
361 s_report = f
"# {s_title}\n\n"
370 price = entry.price_total
376 if (user_report
is None)
or (user_report == user):
380 min_date = min(min_date, date)
384 max_date = max(max_date, date)
385 if entry.group
not in [I_GROUP_1, I_GROUP_2]:
390 if user != EUser.FREE:
391 if user
not in d_user_item:
392 d_user_item[user] = copy.deepcopy(D_GROUP_SUMMARY)
395 if name
in d_user_item[user][group][SALES_KEY]:
396 d_user_item[user][group][SALES_KEY][name] += count
398 d_user_item[user][group][SALES_KEY][name] = count
399 d_user_item[user][group][SUM_KEY] += price
402 if name
in d_total_items[group][SALES_KEY]:
403 d_total_items[group][SALES_KEY][name] += count
405 d_total_items[group][SALES_KEY][name] = count
406 d_total_items[group][SUM_KEY] += price
408 if name
in d_free_items[group][SALES_KEY]:
409 d_free_items[group][SALES_KEY][name] += count
411 d_free_items[group][SALES_KEY][name] = count
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"
421 for file
in l_files[1:]:
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"
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}_"
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)]
440 s_total_sum = f
"{total_sum:.2f}"
441 if user_report
is None:
443 s_report += f
"{self.ui.model.c_language.get_language_text(['Total sum', 'Gesamtsumme'])}: {s_total_sum} {S_UNIT}\n"
445 s_report += f
"{self.ui.model.c_language.get_language_text(['inc. EC', 'ink. EC'])}: {f_ec_sum:.2f} {S_UNIT}\n"
447 s_total_text = self.
group_items(d_total_items, b_total_values=
True)
448 if len(d_user_item) > 1:
449 s_report += s_total_text
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
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:
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):
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:
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"
485 b_missing_sales =
False
486 if not b_combine
and (user_report
is None):
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)
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"
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]
512 @brief Clear sales in settings
514 for i_user
in range(I_NUMBER_OF_SALES):
515 self.
l_sales[i_user] = F_DEFAULT_SALES
516 write_sales(i_user, F_DEFAULT_SALES)
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]]:
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
525 l_all_item: list[ItemReport] = []
526 l_header: list[str] = []
527 b_log_data_readable =
False
529 for i, file
in enumerate(l_files):
530 fix_data =
None if (l_fix_data
is None)
else l_fix_data[i]
532 if b_log_data_readable:
537 return b_log_data_readable, l_all_item, l_header
540 fix_data: Optional[list[list[str]]] =
None) -> tuple[bool, list[ItemReport], list[str]]:
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
548 s_device_suffix: str |
None =
None
550 s_device_suffix = os.path.basename(s_file_name)
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:
556 s_device_suffix = l_device_suffix[-1]
558 s_device_suffix =
None
564 l_raw_data = fix_data
566 for i, row
in enumerate(l_raw_data):
569 if (s_device_suffix
is not None)
and (user == EUser.LOCAL):
570 user = f
"{user}_{s_device_suffix}"
572 price_total = float(row[4])
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])
579 except BaseException:
582 b_log_data_readable =
False
584 b_log_data_readable =
True
586 return b_log_data_readable, l_item, l_header
590 @brief Read printouts from log file.
591 @param s_file_name : file to read
592 @return return printed items
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=
"|")
604 @brief Write printouts to log file
605 @param l_items : articles to write to log file
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
properties of logged article
Report class to log and create sales articles.
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.
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.
bool b_log_valid_verified
None clear_setting_log(self)
Clear sales in settings.
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.
bool check_setting_sales_exist(self)
Write printed articles to log file.
bool check_sales_exist(self)
Check if sales in log file or settings exist.
None clear_print_items(self)
clear items to hold in storage before print out
list[list[str]] read_data_from_print_file(self, str s_file_name=S_PRINT_FILE)
Read printouts from log file.
None __init__(self, "MainWindow" ui)
None write_data_to_file(self, list[list[Any]] l_items)
Write printouts to log file.
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.
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.
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.
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.