Advertisement
Guest User

Untitled

a guest
Sep 17th, 2019
132
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.96 KB | None | 0 0
  1. # -*- coding: utf-8 -*-
  2. # from tqdm import tqdm
  3. # import fix_price_email
  4. # from abc import ABCMeta, abstractmethod
  5.  
  6. import datetime
  7. import logging
  8. import os
  9. import time
  10.  
  11. import pandas as pd
  12.  
  13. logging.basicConfig(filename='CSSI Slip Log.log', level=logging.DEBUG)
  14.  
  15.  
  16. class 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.  
  237. class 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.  
  326. class 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.  
  357. def 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.  
  367. if __name__ == '__main__':
  368. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement