from db import dbGetMany, dbGetOne import datetime from loguru import logger from decimal import * from utils import getParam from Cheetah.Template import Template import xlsxwriter from dateutil.relativedelta import relativedelta EPSILON = Decimal('0.000000001') def perform(dbh, params): year = getParam(params, 'year', datetime.datetime.today().year) startDate = datetime.datetime(year, 1, 1, 0, 0, 0) endDate = datetime.datetime(year, 12, 31, 23, 59, 59) premises = (1, 2) houses = {} for premise in premises: # get overhead sums by object, category and timespan overheadItems = dbGetMany( dbh, { "statement": """ select (coalesce(sum(ae.amount), 0::numeric(10,2)) * -1) as sum, aec.description as category, aec.considerminusarea as considerminusarea from account_t a, premise_t p, account_entry_t ae, account_entry_category_t aec where p.account = a.id and ae.account = a.id and aec.overhead_relevant = 't' and ae.account_entry_category = aec.id and ae.fiscal_year = %(year)s and p.id = %(premise)s group by category, considerminusarea union select 0 as sum, aec.description as category, aec.considerminusarea as considerminusarea from account_t a, premise_t p, account_entry_t ae, account_entry_category_t aec where p.account = a.id and ae.account = a.id and aec.overhead_relevant = 't' and aec.id not in (select distinct account_entry_category from account_entry_t where fiscal_year = %(year)s and account = p.account) and ae.fiscal_year = %(year)s and p.id = %(premise)s group by category, considerminusarea union select 120 as sum, '16. Waschmaschine' as category, false as considerminusarea from premise_t where id = %(premise)s order by category """, "params": { "year": year, "premise": premise } } ) # get areas and factors totalArea = {} flatArea = dbGetOne( dbh, { "statement": """ select sum(f.area) as flat_area from premise_t p, flat_t f where f.premise = p.id and p.id = %(premise)s """, "params": { "premise": premise } } ) totalArea['flat_area'] = flatArea['flat_area'] commercialArea = dbGetOne( dbh, { "statement": """ select coalesce(sum(c.area), 0) as commercial_area from premise_t p full outer join commercial_premise_t c on c.premise = p.id where p.id = %(premise)s """, "params": { "premise": premise } } ) totalArea['commercial_area'] = commercialArea['commercial_area'] minusAreaAndDetails = dbGetOne( dbh, { "statement": """ select p.minus_area as minus_area, p.id as house_id, p.description as house from premise_t p where p.id = %(premise)s """, "params": { "premise": premise } } ) totalArea['minus_area'] = minusAreaAndDetails['minus_area'] details = { 'id': minusAreaAndDetails['house_id'], 'description': minusAreaAndDetails['house'] } totalArea['other_area'] = totalArea['commercial_area'] + totalArea['minus_area'] totalArea['total_area'] = totalArea['flat_area'] + totalArea['other_area'] totalArea['flat_factor'] = totalArea['flat_area'] / totalArea['total_area'] totalArea['other_factor'] = totalArea['other_area'] / totalArea['total_area'] totalArea['factor_check'] = totalArea['flat_factor'] + totalArea['other_factor'] totalSum = Decimal('0') flatSum = Decimal('0') otherSum = Decimal('0') for overheadItem in overheadItems: totalSum += overheadItem['sum'] overheadItem['flat_part'] = overheadItem['sum'] * totalArea['flat_factor'] if overheadItem['considerminusarea'] else overheadItem['sum'] flatSum += overheadItem['flat_part'] overheadItem['other_part'] = overheadItem['sum'] * totalArea['other_factor'] if overheadItem['considerminusarea'] else Decimal('0') otherSum += overheadItem['other_part'] verifyDifference = totalSum - flatSum - otherSum logger.debug(f"{totalSum=}, {verifyDifference=}") if abs(verifyDifference) > EPSILON: raise Exception(f"Verify Difference is too large: {premise=}, {verifyDifference=}") umlageAusfallWagnis = flatSum * Decimal('0.02') flatSum2 = flatSum + umlageAusfallWagnis partByMonthArea = flatSum2 / Decimal('12') / totalArea['flat_area'] houses[details['id']] = { 'details': details, 'areas': totalArea, 'overhead_items': overheadItems, 'flat_sum': flatSum, 'flat_sum_2': flatSum2, 'other_sum': otherSum, 'total_sum': totalSum, 'umlage_ausfall_wagnis': umlageAusfallWagnis, 'part_by_montharea': partByMonthArea, 'year': year } logger.info(f"{houses=}") printOverviews = getParam(params, 'printOverviews', True) if printOverviews: overviewTemplate = getParam(params, 'overviewTemplate', 'betriebskostenuebersicht.tmpl') overviewPrefix = getParam(params, 'overviewPrefix', 'overview') overviewSuffix = getParam(params, 'overviewSuffix', 'tex') for house in houses.values(): logger.debug(f"Processing item: {house}") outputFile = f"./output/{overviewPrefix}-{house['details']['id']}.{overviewSuffix}" tmpl = Template(file=overviewTemplate, searchList=[ house ]) logger.debug(tmpl) with open(outputFile, 'w') as f: f.write(str(tmpl)) # get flat tenants by object and timespan with paid overhead and due overhead tenants = dbGetMany( dbh, { "statement": """ select t.id as tenant_id, t.salutation as tenant_salutation, t.firstname as tenant_firstname, t.lastname as tenant_lastname, t.address1 as tenant_address1, t.address2 as tenant_address2, t.address3 as tenant_address3, t.zip as tenant_zip, t.city as tenant_city, f.id as flat_id, f.description as flat, f.area as flat_area, p.id as house_id, p.description as house, ty.startdate as startdate, ty.enddate as enddate, t.account as tenant_account, fe.amount as fee, fe.fee_type as fee_type from tenant_t t, premise_t p, flat_t f, tenancy_t ty, tenancy_fee_mapping_t tyfm, fee_t fe where ty.tenant = t.id and ty.flat = f.id and ty.startdate <= %(startDate)s and (ty.enddate >= %(endDate)s or ty.enddate is null) and f.premise = p.id and p.id in %(premises)s and tyfm.tenancy = ty.id and tyfm.fee = fe.id order by house_id, tenant_id """, "params": { "startDate": startDate, "endDate": endDate, "premises": premises } } ) letters = [] for tenant in tenants: letter = {} paidTotal = dbGetOne( dbh, { "statement": """ SELECT sum(amount) AS sum FROM account_entry_t WHERE account = %(account)s AND account_entry_category = 2 AND fiscal_year = %(year)s """, "params": { "account": tenant['tenant_account'], "year": year } } ) tenant['paid_total'] = paidTotal['sum'] receivableFee = dbGetOne( dbh, { "statement": """ SELECT sum(amount) * -1 AS sum FROM account_entry_t WHERE account = %(account)s AND account_entry_category = 3 AND fiscal_year = %(year)s """, "params": { "account": tenant['tenant_account'], "year": year } } ) tenant['receivable_fee'] = receivableFee['sum'] delta = relativedelta((tenant['enddate'] or datetime.date(year, 12, 31)), (tenant['startdate'] if (tenant['startdate'].year == year) else datetime.date(year, 1, 1))) tenant['rent_time'] = delta.months if delta.days == 0 else delta.months + 1 tenant['paid_overhead'] = paidTotal['sum'] - receivableFee['sum'] letter['tenant'] = tenant letter['year'] = year letter['flat_area'] = houses[tenant['house_id']]['areas']['flat_area'] letter['receivable_overhead'] = tenant['flat_area'] * houses[tenant['house_id']]['part_by_montharea'] * tenant['rent_time'] letter['unbalanced_overhead'] = tenant['paid_overhead'] - letter['receivable_overhead'] letter['unbalanced_overhead_unsigned'] = abs(letter['unbalanced_overhead']) letter['total_overhead'] = houses[tenant['house_id']]['flat_sum_2'] letters.append(letter) logger.info(f"{letter=}") printLetters = getParam(params, 'printLetters', True) if printLetters: letterTemplate = getParam(params, 'letterTemplate', 'jahresabrechnung.tmpl') letterPrefix = getParam(params, 'letterPrefix', 'letter') letterSuffix = getParam(params, 'letterSuffix', 'tex') for letter in letters: logger.debug(f"Processing item: {letter}") outputFile = f"./output/{letterPrefix}-{letter['tenant']['tenant_id']}.{letterSuffix}" tmpl = Template(file=letterTemplate, searchList=[ letter ]) logger.debug(tmpl) with open(outputFile, 'w') as f: f.write(str(tmpl)) if printOverviews: tenantsOverviewPrefix = getParam(params, 'tenantsOverviewPrefix', 'tenantsOverview') tenantsOverviewSuffix = getParam(params, 'tenantsOverviewSuffix', 'xlsx') logger.debug(f"Processing letters: {letters}") outputFile = f"./output/{tenantsOverviewPrefix}-{year}.{tenantsOverviewSuffix}" workbook = xlsxwriter.Workbook(outputFile) worksheet = workbook.add_worksheet() worksheet.write(0, 0, 'id') worksheet.write(0, 1, 'lastname') worksheet.write(0, 2, 'firstname') worksheet.write(0, 3, 'house') worksheet.write(0, 4, 'overhead_part_by_montharea') worksheet.write(0, 5, 'flat') worksheet.write(0, 6, 'flat_area') worksheet.write(0, 7, 'fee') worksheet.write(0, 8, 'rent_time') worksheet.write(0, 9, 'paid_total') worksheet.write(0, 10, 'receivable_fee') worksheet.write(0, 11, 'paid_overhead') worksheet.write(0, 12, 'receivable_overhead') worksheet.write(0, 13, 'unbalanced_overhead') row = 1 for entry in letters: worksheet.write(row, 0, entry['tenant']['tenant_id']) worksheet.write(row, 1, entry['tenant']['tenant_lastname']) worksheet.write(row, 2, entry['tenant']['tenant_firstname']) worksheet.write(row, 3, entry['tenant']['house']) worksheet.write(row, 4, houses[entry['tenant']['house_id']]['part_by_montharea']) worksheet.write(row, 5, entry['tenant']['flat']) worksheet.write(row, 6, entry['tenant']['flat_area']) worksheet.write(row, 7, entry['tenant']['fee']) worksheet.write(row, 8, entry['tenant']['rent_time']) worksheet.write(row, 9, entry['tenant']['paid_total']) worksheet.write(row, 10, entry['tenant']['receivable_fee']) worksheet.write(row, 11, entry['tenant']['paid_overhead']) worksheet.write(row, 12, entry['receivable_overhead']) worksheet.write(row, 13, entry['unbalanced_overhead']) row += 1 workbook.close()