· 6 years ago · Sep 17, 2019, 03:50 PM
1# -*- coding: utf-8 -*-
2# from tqdm import tqdm
3# import fix_price_email
4# from abc import ABCMeta, abstractmethod
5
6import datetime
7import logging
8import os
9import time
10
11import pandas as pd
12
13logging.basicConfig(filename='CSSI Slip Log.log', level=logging.DEBUG)
14
15
16class Slip:
17 """
18 Class for slip creation. Transforms json to text and puts text file in a folder.
19 Input:
20 <root:str> - path to the cssi_folder
21 <slip_path:str> - path + filename of a slip
22 """
23
24 @staticmethod
25 def inn_check(inn):
26 """
27 Returns True if inn of the given slip is in the list of the FixPrice inns.
28 :param inn:
29 :return:
30 """
31 inn_verified = pd.read_excel('inn_verified.xlsx')
32 inn_list = inn_verified['inn'].to_list()
33 if int(inn) not in inn_list:
34 return False
35 return True
36
37 @classmethod
38 def check_json(cls, root, json_path):
39 """
40 Method to see if we can create a slip in the first place. If False then class instance won't appear.
41 It checks if json file is empty, if it can be read and verifies inn. Removes slip if inn is wrong.
42 :param root:
43 :param json_path:
44 :return: bool
45 """
46 logging.info(f'Starting check {json_path}')
47 try:
48 json_table = pd.read_json(json_path, encoding='utf-8', orient='values')
49 except PermissionError:
50 pass
51 json_table_row = json_table.loc[0, :]
52 # try:
53 # json_table = pd.read_json(json_path, encoding='utf-8', orient='values')
54 # except:
55 # logging.warning(f'Error in file {json_path}. Json could not be read')
56 # return False
57 # try:
58 # json_table_row = json_table.loc[0, :]
59 # except:
60 # logging.warning(f'Error in file {json_path}. This slip was ignored')
61 # return False
62 if json_table.empty:
63 return False
64
65 inn = str(json_table_row['userInn'])
66 if cls.inn_check(inn):
67 pass
68 else:
69 os.remove(json_path)
70 return False
71
72 return True
73
74 def __init__(self, root, json_path):
75 """
76 :param root: cssi folder
77 :param json_path: path to the slip
78 """
79 logging.info(f'Reading {json_path}')
80 self.root = root
81 self.json_path = json_path
82 self.json_table = pd.read_json(json_path, encoding='utf-8', orient='values')
83 self.item_table = self.get_items(self.json_table)
84 self.total = str(self.json_table.iloc[0]['ecashTotalSum'] + self.json_table.iloc[0]['cashTotalSum'])
85 self.json_table_row = self.json_table.loc[0, :]
86 self.cassa_code = str(self.json_table_row['kktRegId'])
87 self.seq_number = str(self.json_table_row['fiscalDocumentNumber'])
88 self.date = self.json_table_row['dateTime']
89 new_date = [str(self.date.year), '{:0>2}'.format(str(self.date.month)), '{:0>2}'.format(str(self.date.day))]
90 time = ['{:0>2}'.format(str(self.date.hour)), '{:0>2}'.format(str(self.date.minute))]
91 self.cssi_date = ''.join(new_date)
92 self.cssi_time = ''.join(time)
93 self.slip_name = f'{self.cassa_code}_{self.seq_number}.txt'
94 self.store_code = self.shop_id_from_slip_address()
95
96 def shop_id_from_slip_address(self):
97 """
98 As input it gets cassa_code and checks with excel file for mapping of cassa to shop_id.
99 If 0 is returned it means that no such mapping exists and cssi_person has to add it manually.
100 :return: <shop_id:str> - shop_id for session folder creation
101 """
102 shop_frame = pd.read_excel('cassa_to_store_id.xlsx', encoding='utf-8')
103 shop_id = shop_frame.loc[shop_frame['cassa'] == int(self.cassa_code)]
104 if shop_id.iloc[0]['store_id'] == 0:
105 # is valueerror suitable?
106 # TODO: create a windows message with buttons
107 # TODO: find appropriate exception
108 logging.warning(f'Error in linkage shop_id_from_slip_address')
109 raise ValueError
110 return str(shop_id.iloc[0]['store_id'])
111
112 @staticmethod
113 def get_items(dataframe):
114 """
115 Extracts a list of jsons from ['items'] column in the original json.
116 Each entry is a line with a product.
117 :param dataframe: pandas df - json file
118 :return: dataframe: pandas df - dataframe with all products from a slip
119 """
120 items = dataframe.loc[:, ['items']]
121 result = pd.DataFrame()
122 for index, row in items.iterrows():
123 new_json = row.to_json()
124 item = pd.read_json(new_json, encoding='utf-8').transpose()
125 result = result.append(item)
126 return result
127
128 @staticmethod
129 def convert_price(price):
130 """
131 Receives price * 100. Converts to decimal format
132 :param price - str
133 :return: str - price for text file
134 """
135 decimal = price[-2:]
136 integer = price[:-2]
137 return integer + ',' + decimal
138
139 @staticmethod
140 def time_in_range(start, end, my_time):
141 """
142 Check if time is in range.
143 :param start: datetime.time format
144 :param end: datetime.time format
145 :param my_time: datetime.time format
146 :return: bool
147 """
148 if start < my_time <= end:
149 return True
150 return False
151
152 @classmethod
153 def get_session(cls, slip_time):
154 """
155 Return text for folder name.
156 :param slip_time: time on the slip
157 :return: str - session time for folder creation
158 """
159 time_on_slip = datetime.time(slip_time.hour, slip_time.minute, slip_time.second)
160 if cls.time_in_range(datetime.time(9, 0, 0), datetime.time(12, 0, 0), time_on_slip):
161 return "_0900_1200"
162 elif cls.time_in_range(datetime.time(12, 0, 0), datetime.time(15, 0, 0), time_on_slip):
163 return "_1200_1500"
164 elif cls.time_in_range(datetime.time(15, 0, 0), datetime.time(18, 0, 0), time_on_slip):
165 return "_1500_1800"
166 elif cls.time_in_range(datetime.time(18, 0, 0), datetime.time(21, 0, 0), time_on_slip):
167 return "_1800_2100"
168 else:
169 return "_out_of_bounds"
170
171 def create_folder(self):
172 """
173 Check if folder exists and if not it is created
174 :return: str - session folder name
175 """
176 chain_name = 'RUSSIAN-FIXPRICE_'
177
178 session = self.get_session(self.date)
179
180 folder = chain_name + self.store_code + '_' + self.cssi_date + session
181 return folder
182
183 def prepare_items(self):
184 """
185 Formats data on the slip.
186 :return: None
187 """
188 for index, row in self.item_table.iterrows():
189 row['name'] = row['name'][:50]
190 row['name'] = '{:<50}'.format(row['name'])
191 row['quantity'] = int(row['quantity'])
192 row['quantity'] = '{:<15}'.format(row['quantity'])
193 row['price'] = self.convert_price(str(row['price']))
194 row['sum'] = row['price']
195 row['price'] = '{:<15}'.format(row['price'])
196
197 def create_txt_slip_head(self):
198 """
199 Creates a head of the slip.
200 :return: None
201 """
202 # 218515031227_65179
203 with open(self.root + f'\\{self.cassa_code}_{self.seq_number}.txt', 'w', encoding='utf-8') as text_file:
204 text_file.write('\ufeff')
205 text_file.write('Store code: ' + self.store_code + '\n')
206 text_file.write('Total: ' + self.convert_price(self.total) + '\n')
207 text_file.write('Date: ' + self.cssi_date + '\n')
208 text_file.write('Time: ' + self.cssi_time + '\n')
209 text_file.write('Cassa code: ' + self.cassa_code + '\n')
210 text_file.write('Seq. number: ' + self.seq_number + '\n')
211 text_file.write('Image file: test_test_test\n')
212
213 def create_txt_slip_body(self):
214 """
215 Creates a table with products that is appended to the head.
216 :return: None
217 """
218 items = self.item_table.loc[:, ['name', 'quantity', 'price', 'sum']]
219 # print(items.head(5))
220 list_items = items.values.tolist()
221 with open(self.root + f'\\{self.cassa_code}_{self.seq_number}.txt', 'a', encoding='utf-8') as text_file:
222 for row in list_items:
223 row.append('\n')
224 line = ''.join(row)
225 text_file.write(line)
226
227 def create_text_file(self):
228 """
229 Groupping function for creation.
230 :return: None
231 """
232 self.create_txt_slip_head()
233 self.create_txt_slip_body()
234 # print('Text file was created')
235
236
237class CashSlipManager:
238 """
239 It has to be a facade for the download manager and json converter.
240 Currently it can create a folder and convert json to text.
241 """
242
243 # @abstractmethod
244 # def download_slips(self, date_start, date_end):
245 # # need slip path
246 # # date_start = int(pd.to_datetime('2019-07-26 00:00:00').timestamp())
247 # # date_end = int(pd.to_datetime('2019-07-31 23:59:59').timestamp())
248 # fix_price_email.main(date_start, date_end)
249 # # TODO: select path to save (in client class)
250 #
251 # # TODO: MAIN TODO IS TO REWRITE fix_price_email as a class with function or just functions
252 #
253 # # TODO: import 1.fix_price and launch main with selected dates
254 #
255 # # TODO: select dates by hand (date_picker for python)
256 # print('All json files from attachments have been downloaded.')
257 @staticmethod
258 def session_folder_create(root, session_folder, now):
259 """
260 Creates a folder for upload session in the cssi. All the session folders created will go in here.
261 :param root: folder of the cssi program
262 :param session_folder - name of the session folder
263 :param now: datetime.datetime format - current time
264 :return: str - output path
265 """
266 chain_name = 'RUSSIAN-FIXPRICE_'
267 date_now = now
268 date_now_date = [str(date_now.year), '{:0>2}'.format(str(date_now.month)), '{:0>2}'.format(str(date_now.day))]
269 date_now_time = ['{:0>2}'.format(str(date_now.hour)), '{:0>2}'.format(str(date_now.minute)),
270 '{:0>2}'.format(str(date_now.second))]
271 date_now_date.extend(date_now_time)
272 time_part = ''.join(date_now_date)
273
274 folder = time_part + '_' + chain_name + 'ILYA'
275 session_path = root + '\\output\\' + folder
276
277 if os.path.isdir(session_path):
278 pass
279 else:
280 os.mkdir(session_path)
281 logging.warning(f'Folder {session_path} was created')
282
283 final_path = session_path + '\\' + session_folder
284
285 if os.path.isdir(final_path):
286 return final_path
287 else:
288 os.mkdir(final_path)
289 logging.warning(f'Folder {final_path} was created')
290 return final_path
291
292 def json_to_txt(self, root_path, filename, session_time):
293 """
294 This does the main lifting. It creates a slip with the interface of Slip class. Creates a folder
295 and moves all the files. Checks for file existence and can delete files.
296 :param root_path:str - file path
297 :param filename:str - filename
298 :param session_time:datetime.datetime format - current time
299 :return: str - fail/success which is used to count errors and number of slips
300 """
301 slip_path = root_path + '\\' + filename
302 if Slip.check_json(root_path, slip_path):
303 new_slip = Slip(root_path, slip_path)
304 else:
305 return 'fail'
306 new_slip.prepare_items()
307 new_slip.create_text_file()
308 new_path = self.session_folder_create(root_path, new_slip.create_folder(), session_time)
309
310 try:
311 os.rename(root_path + '\\' + new_slip.slip_name, new_path + '\\' + new_slip.slip_name)
312 except FileExistsError:
313 # print('Cash slip already exists.')
314 os.remove(root_path + '\\' + new_slip.slip_name)
315 try:
316 os.rename(slip_path, root_path + '\\parsed\\' + filename)
317 except FileExistsError:
318 # print('Cash slip already exists.')
319 os.remove(new_path + '\\' + new_slip.slip_name)
320 # except WindowsError:
321 # # file not found
322 # pass
323 return 'success'
324
325
326class CssiUser(CashSlipManager):
327 """
328 Worker class that inherits functions from the SlipManager. This class is a client class and does logging.
329 """
330
331 def __init__(self, output_path):
332 self.output_path = output_path
333
334 def cssi_input(self):
335 slip_number = len(os.listdir(self.output_path))
336 count = 0
337 errors = 0
338 error_files = []
339 start = time.perf_counter()
340 current_time = datetime.datetime.now()
341 for file in os.listdir(self.output_path):
342 if self.json_to_txt(self.output_path, file, current_time) == 'success':
343 count += 1
344 logging.warning(f'Cash slip {count}/{slip_number} was created')
345 else:
346 errors += 1
347 error_files.append(self.output_path + '\\' + file)
348 logging.warning(f'Cash slip was not created.')
349 logging.warning(f'Total cash slips run: {count}/{slip_number}')
350 logging.warning(f'Number of errors in slips: {errors}. Problematic files are:')
351 for file in error_files:
352 logging.warning(f'{file}')
353 end = time.perf_counter()
354 logging.warning(f'Total time taken {int(end) - int(start)} seconds')
355
356
357def main():
358 """
359 Launch script
360 :return: None
361 """
362 out = 'C:\\Users\\SaIl9001\\AppData\\Local\\Programs\\Python\\Python37-32\\cssi_python_script\\output'
363 user = CssiUser(out)
364 user.cssi_input()
365
366
367if __name__ == '__main__':
368 main()