Spaces:
Build error
Build error
| import os | |
| import subprocess | |
| import sys | |
| import re | |
| import pandas as pd | |
| from datetime import datetime | |
| import logging | |
| # Setting up logger | |
| logger = logging.getLogger(__name__) | |
| logger.setLevel(logging.DEBUG) | |
| formatter = logging.Formatter("%(asctime)s:%(name)s:%(lineno)d:%(levelname)s:%(message)s") | |
| file_handler = logging.FileHandler(f'parse_payslip_info_log_{datetime.now().strftime("%d_%m_%Y__%H_%M_%S")}.log') | |
| file_handler.setFormatter(formatter) | |
| stdout_handler = logging.StreamHandler(sys.stdout) | |
| stdout_handler.setFormatter(formatter) | |
| logger.addHandler(file_handler) | |
| if len(sys.argv) > 1: | |
| if "--verbose" in sys.argv or "-v" in sys.argv: | |
| logger.addHandler(stdout_handler) | |
| def convert_pdf_to_text(): | |
| ''' | |
| convert_pdf_to_text() | |
| Converts all pdf files in the current directory to text files | |
| Output directory: .\converted_pdfs | |
| ''' | |
| # directory to hold converted text files | |
| logger.info("Creating converted_pdfs directory") | |
| if os.system("mkdir converted_pdfs") != 0: | |
| logger.error("Failed to create converted_pdfs directory") | |
| sys.exit() | |
| # list of pdf files in dir/sub-dir and save them to a text file | |
| logger.info("Gathering list of full path to pdf files in the current directory/sub-directory") | |
| os.system("dir /s /b *.pdf > allpdf.txt") | |
| list_fnames = [] | |
| # put \n separated file path in a list | |
| logger.info("Saving pdf file names to a list") | |
| try: | |
| with open('allpdf.txt', 'r') as fh: | |
| list_fnames = list(fh.read().split('\n')) | |
| except FileNotFoundError: | |
| logger.error("Unable to open file: allpdf.txt") | |
| sys.exit() | |
| err_count = 0 | |
| # converting files one by one | |
| logger.info("Generating text files from pdf") | |
| for fname in list_fnames: | |
| if fname: | |
| target_text_fname = f"{get_fname_without_ext(fname)}.txt" | |
| target_text_path = os.path.join('.\converted_pdfs', target_text_fname) | |
| ret = subprocess.run(['bin64\pdftotext.exe', fname, target_text_path], capture_output=True) | |
| if ret.returncode != 0: | |
| logger.error(f"Error converting: {target_text_path}") | |
| err_count += 1 | |
| # saving list of converted text files | |
| logger.info("Gathering list of text file names") | |
| os.system("dir converted_pdfs\*.txt /b > alltexts.txt") | |
| return err_count | |
| def get_fname_without_ext(fname): | |
| ''' | |
| get_fname_without_ext(fname) | |
| Returns the filename (without extension) from a filepath | |
| Ex: Return 'ebook' from d:\pdffiles\ebook.pdf | |
| ''' | |
| match = "" | |
| match = re.search(r'(Payslip_.+)(.pdf)', fname) | |
| if match: | |
| return match.group(1) | |
| else: | |
| return "" | |
| def get_list_of_converted_files(): | |
| ''' | |
| get_list_of_converted_files() | |
| Returns list of full path of converted text files | |
| ''' | |
| list_text_fnames = [] | |
| def append_path(fname): | |
| return os.path.join(".\converted_pdfs", fname) | |
| logger.info("Reading alltexts.txt, appending converted_pdfs directory name") | |
| try: | |
| with open("alltexts.txt", 'r') as fh: | |
| list_text_fnames = list(fh.read().split('\n')) | |
| except FileNotFoundError: | |
| logger.error("alltexts.txt not found") | |
| sys.exit() | |
| return list(map(append_path, list_text_fnames)) | |
| def format_number_str(s): | |
| ''' | |
| format_number_str(s) | |
| Converts number string to float | |
| Ex: 2,345.00 to 2345.00 | |
| ''' | |
| if s != "": | |
| return float(s.replace(",", "").replace(" ", "")) | |
| else: | |
| return 0 | |
| def month_no_to_name(mnum): | |
| ''' | |
| month_no_to_name(mnum) | |
| Returns 3 letter month name from month number | |
| Ex: 1 -> Jan, 2 -> Feb, 12 -> Dec | |
| ''' | |
| months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] | |
| return months[mnum-1] | |
| class Payslip: | |
| ''' | |
| Payslip class | |
| Contains methods to extract payslip details from a converted text file | |
| ''' | |
| def __init__(self): | |
| self.pay_period = "" | |
| self.pay_date = "" | |
| self.epf_no = "" | |
| self.uan_no = "" | |
| self.basic_salary = "" | |
| self.gross_salary = "" | |
| self.net_salary = "" | |
| self.gross_salary_ytd = "" | |
| self.pf_amount = "" | |
| self.pf_ytd = "" | |
| self.income_tax = "" | |
| self.income_tax_ytd = "" | |
| self.raw_payslip_text = "" | |
| def read_text(self, fname): | |
| try: | |
| with open(fname, 'r') as fh: | |
| self.raw_payslip_text = fh.read() | |
| self.pay_period = self.get_pay_period() | |
| self.pay_date = self.get_pay_date() | |
| self.epf_no = self.get_epf_number() | |
| self.uan_no = self.get_uan_number() | |
| self.basic_salary = self.get_basic_salary() | |
| self.gross_salary = self.get_gross_sal() | |
| self.net_salary = self.get_net_sal() | |
| self.gross_salary_ytd =self.get_gross_sal_ytd() | |
| self.pf_amount =self.get_pf() | |
| self.pf_ytd = self.get_pf_ytd() | |
| self.income_tax = self.get_income_tax() | |
| self.income_tax_ytd = self.get_income_tax_ytd() | |
| except FileNotFoundError: | |
| logger.error(f"File not found: {fname}") | |
| def get_pay_period(self): | |
| match = re.search(r'Pay\sPeriod\s:\s?([\d.]+[\s\-]+[\d.]+)', self.raw_payslip_text) | |
| if match: | |
| return match.group(1) | |
| else: | |
| return "" | |
| def get_pay_date(self): | |
| match = re.search(r'Pay\sDate\n\n:\s?([\d+.]+)', self.raw_payslip_text) | |
| if match: | |
| return match.group(1) | |
| else: | |
| return "" | |
| def get_epf_number(self): | |
| match = re.search(r'Emp\sPF\sNumber:\s?([\w\/]+)', self.raw_payslip_text) | |
| if match: | |
| return match.group(1) | |
| else: | |
| return "" | |
| def get_uan_number(self): | |
| match = re.search(r'UAN[\n]+:\s?(\d+)', self.raw_payslip_text) | |
| if match: | |
| return match.group(1) | |
| else: | |
| return "" | |
| def get_basic_salary(self): | |
| match = re.search(r'Basic\sSalary\n+([\d,.]+)', self.raw_payslip_text) | |
| if match: | |
| return match.group(1) | |
| else: | |
| return "" | |
| def get_gross_sal(self): | |
| match = re.search(r'Total\sGross\n+([\d,.]+)', self.raw_payslip_text) | |
| if match: | |
| return match.group(1) | |
| else: | |
| return "" | |
| def get_net_sal(self): | |
| match = re.search(r'NET\sPAY\n+([\d,.]+)', self.raw_payslip_text) | |
| if match: | |
| return match.group(1) | |
| else: | |
| return "" | |
| def get_gross_sal_ytd(self): | |
| match = re.search(r'YTD\sGROSS\n+([\d,.]+)', self.raw_payslip_text) | |
| if match: | |
| return match.group(1) | |
| else: | |
| return "" | |
| def get_pf(self): | |
| match = re.search(r'Provident\sFund\n+([\d.,]+)', self.raw_payslip_text) | |
| if match: | |
| return match.group(1) | |
| else: | |
| return "" | |
| def get_pf_ytd(self): | |
| match = re.search(r'YTD\sEmployee\sPF\n+([\d.,]+)', self.raw_payslip_text) | |
| if match: | |
| return match.group(1) | |
| else: | |
| return "" | |
| def get_income_tax(self): | |
| match = re.search(r'Income\sTax\n+([\d,.]+)', self.raw_payslip_text) | |
| if match: | |
| return match.group(1) | |
| else: | |
| return "" | |
| def get_income_tax_ytd(self): | |
| match = re.search(r'YTD\sTAX\n+([\d,.]+)', self.raw_payslip_text) | |
| if match: | |
| return match.group(1) | |
| else: | |
| return "" | |
| payslip_details = { | |
| "pay_period": [], | |
| "pay_date": [], | |
| "basic_salary": [], | |
| "gross_salary": [], | |
| "net_salary": [], | |
| "gross_salary_ytd": [], | |
| "pf_amount": [], | |
| "pf_ytd": [], | |
| "income_tax": [], | |
| "income_tax_ytd": [], | |
| "epf_no": [], | |
| "uan_no": [], | |
| } | |
| logger.info("Process started") | |
| logger.info("Converting pdf to text") | |
| convert_pdf_to_text() | |
| list_txt_fnames = get_list_of_converted_files() | |
| pay = Payslip() | |
| logger.info("Saving payslip details from each text file to dictionary") | |
| # Saving payslip details from each text file to dictionary | |
| for fname in list_txt_fnames: | |
| pay.read_text(fname) | |
| payslip_details["pay_period"].append(pay.pay_period) | |
| payslip_details["pay_date"].append(pay.pay_date) | |
| payslip_details["epf_no"].append(pay.epf_no) | |
| payslip_details["uan_no"].append(pay.uan_no) | |
| payslip_details["basic_salary"].append(pay.basic_salary) | |
| payslip_details["gross_salary"].append(pay.gross_salary) | |
| payslip_details["net_salary"].append(pay.net_salary) | |
| payslip_details["gross_salary_ytd"].append(pay.gross_salary_ytd) | |
| payslip_details["pf_amount"].append(pay.pf_amount) | |
| payslip_details["pf_ytd"].append(pay.pf_ytd) | |
| payslip_details["income_tax"].append(pay.income_tax) | |
| payslip_details["income_tax_ytd"].append(pay.income_tax_ytd) | |
| logger.info("Creating dataframe from dictionary") | |
| # creating dataframe from dictionary | |
| pay_df = pd.DataFrame.from_dict(payslip_details) | |
| logger.info("Formatting columns containing numeric data") | |
| # Formatting columns containing numeric data | |
| # converting object to float | |
| pay_df['basic_salary'] = pay_df['basic_salary'].apply(lambda x: format_number_str(x)) | |
| pay_df['net_salary'] = pay_df['net_salary'].apply(lambda x: format_number_str(x)) | |
| pay_df['gross_salary'] = pay_df['gross_salary'].apply(lambda x: format_number_str(x)) | |
| pay_df['gross_salary_ytd'] = pay_df['gross_salary_ytd'].apply(lambda x: format_number_str(x)) | |
| pay_df['pf_amount'] = pay_df['pf_amount'].apply(lambda x: format_number_str(x)) | |
| pay_df['pf_ytd'] = pay_df['pf_ytd'].apply(lambda x: format_number_str(x)) | |
| pay_df['income_tax'] = pay_df['income_tax'].apply(lambda x: format_number_str(x)) | |
| pay_df['income_tax_ytd'] = pay_df['income_tax_ytd'].apply(lambda x: format_number_str(x)) | |
| logger.info("Creating Series to hold year and month") | |
| # series to hold month and year | |
| years = pay_df['pay_date'].apply(lambda x: re.sub(r'\d+.\d+.(\d{4})', r'\1',x)) | |
| months = pay_df['pay_date'].apply(lambda x: month_no_to_name(int(re.sub(r'\d+.(\d+).\d{4}', r'\1',x)))) | |
| logger.info("Appending year and month column to the start") | |
| # appending year and month to the start | |
| pay_df.insert(0, 'year', years) | |
| pay_df.insert(1, 'months', months) | |
| logger.info("Exporting to Excel") | |
| # exporting to Excel | |
| pay_df.to_excel("payslips.xlsx", index=False) | |
| logger.info("Process completed successfully") | |