# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import calendar from decimal import Decimal from datetime import date from trytond.model import ModelView, fields, Workflow from trytond.pool import Pool, PoolMeta from trytond.report import Report from trytond.wizard import Wizard, StateView, Button, StateAction, StateReport, StateTransition from trytond.transaction import Transaction from trytond.pyson import Eval from trytond.modules.staff_payroll import Period import math from trytond.modules.staff_payroll import PayrollReport __all__ = [ 'Payroll', 'PayrollGlobalStart', 'PayrollGlobal', 'PayrollGlobalReport', 'PayrollPaymentReport', 'PayrollPayment', 'PayrollPaymentStart', 'PayrollPaycheckStart', 'PayrollPaycheckReport', 'PayrollPaycheck', 'PayrollSheetReport', 'PayrollSheet', 'PayrollLine', 'PayrollSheetStart', 'PayrollGroupStart', 'PayrollGroup', 'PayrollByPeriodDynamic', 'OpenPayrollByPeriod', 'OpenPayrollByPeriodStart', 'FixPayrollStart', 'FixPayroll', 'Exo2276Start', 'Exo2276', 'Exo2276Report', 'IncomeWithholdings', 'ExportMovesReport', 'IncomeWithholdingsStart', 'IncomeWithholdingsReport', 'PayrollExportStart', 'PayrollExport', 'PayrollExportReport' ] STATES = {'readonly': (Eval('state') != 'draft')} _ZERO = Decimal('0.0') PAYMENTS = ['salary', 'bonus', 'reco', 'recf', 'hedo', 'heno', 'dom', 'hedf', 'henf'] SOCIAL_SEGURITY = ['risk', 'health', 'retirement', 'box_family', 'sena', 'icbf'] ENTITY_ACCOUNTS = { '806008394': (23700501, 72056901), '860066942': (23700502, 72056902), '890300625': (23700503, 72056903), '900226715': (23700504, 72056904), '830003564': (23700505, 72056905), '800251440': (23700506, 72056906), '800088702': (23700507, 72056907), '901097473': (23700508, 72056908), '900156264': (23700509, 72056909), '800130907': (23700510, 72056910), '860045904': (23700511, 72056911), '890102044': (23700514, 72056914), '900336004': (23803001, 72057001), '800149496': (23803002, 72057002), '800144331': (23803004, 72057004), '800229739': (23803005, 72057005), '860013570': (2370100101, 72057201), '890102002': (2370100102, 72057202), '890903790': (23700601, 72057101), '830113831': (23700501, 72056901), '804002105': (23700513, 72056913), '900298372': (23700513, 72056913), '830009783': (23700516, 72056916), '800148514': (23803003, 72057003), '891780093': (2370100103, 72057203) } class PayrollLine(metaclass=PoolMeta): __name__ = "staff.payroll.line" def get_amount(self, name): amount = super(PayrollLine, self).get_amount(name) if amount and self.wage_type and self.wage_type.round_amounts: if self.wage_type.round_amounts == 'above_amount': amount = math.ceil(float(amount) / 100.0) * 100 if self.wage_type.round_amounts == 'under_amount': amount = math.floor(float(amount) / 100.0) * 100 if self.wage_type.round_amounts == 'automatic': amount = round(amount, -2) return amount class Payroll(metaclass=PoolMeta): __name__ = "staff.payroll" last_payroll = fields.Boolean('Last Payroll', states=STATES, select=True) ibc = fields.Function(fields.Numeric('IBC'), 'on_change_with_ibc') health_amount = fields.Function(fields.Numeric('EPS Amount'), 'get_non_fiscal_amount') retirement_amount = fields.Function(fields.Numeric('AFP Amount'), 'get_non_fiscal_amount') risk_amount = fields.Function(fields.Numeric('ARL Amount'), 'get_non_fiscal_amount') box_family_amount = fields.Function(fields.Numeric('Box Amount'), 'get_non_fiscal_amount') absenteeism_days = fields.Integer("Absenteeism Days", states=STATES) department = fields.Many2One('company.department', 'Department', required=False, depends=['employee']) @classmethod def __setup__(cls): super(Payroll, cls).__setup__() table = cls.__table__() cls._error_messages.update({ 'period_without_contract': ('The period selected without contract' ' for the employee!'), } ) # cls._sql_constraints += [ # ('employee_period_contract_uniq', Unique(table, table.contract, table.employee, table.period, table.project), # 'Already exists one payroll for this employee with this contract in this period!'), # ] @fields.depends('end', 'date_effective') def on_change_with_date_effective(self): if self.end: return self.end @fields.depends('employee', 'department') def on_change_employee(self): if self.employee and self.employee.department: self.department = self.employee.department.id @fields.depends('period', 'employee', 'start', 'end', 'contract', 'description', 'date_effective', 'last_payroll') def on_change_period(self): if not self.period: return self.date_effective = self.period.end self.on_change_employee() #Search last contract contract = self.search_contract_on_period(self.employee, self.period) start_date = None end_date = None self.contract = contract contract_end_date = None if contract: if not contract.end_date: end_date = self.period.end if self.period.start >= contract.start_date: start_date = self.period.start elif contract.start_date >= self.period.start and \ contract.start_date <= self.period.end: start_date = contract.start_date else: contract_end_date = contract.finished_date if contract.finished_date else contract.end_date if contract.start_date <= self.period.start and \ contract_end_date >= self.period.end: start_date = self.period.start end_date = self.period.end elif contract.start_date >= self.period.start and \ contract_end_date <= self.period.end: start_date = contract.start_date end_date = contract_end_date elif contract.start_date >= self.period.start and \ contract.start_date <= self.period.end and \ contract_end_date >= self.period.end: start_date = contract.start_date end_date = self.period.end elif contract.start_date <= self.period.start and \ contract_end_date >= self.period.start and \ contract_end_date <= self.period.end: start_date = self.period.start end_date = contract_end_date if start_date and end_date: self.start = start_date self.end = end_date if contract_end_date == self.end: self.last_payroll = True if not start_date or not end_date: self.period = None self.description = None self.raise_user_error('period_without_contract') if not self.description and self.period: self.description = self.period.description def _get_line_quantity(self, quantity_days, wage, extras, discount): quantity_days = self.get_days(self.start, self.end, wage) quantity = super(Payroll, self)._get_line_quantity(quantity_days, wage, extras, discount) return quantity @fields.depends('start', 'end', 'employee', 'period', 'contract') def get_days(self, start, end, wage=None): adjust = 1 adjust_days_worked = False if self.contract and self.contract.position: adjust_days_worked = self.contract.position.adjust_days_worked else: if self.employee.position: adjust_days_worked = self.employee.position.adjust_days_worked if (adjust_days_worked) or (wage and wage.adjust_days_worked): if end.day == 31 or (end.month == 2 and \ (end.day == 28 or end.day == 29)): adjust = 31 - end.day quantity_days = (end - start).days + adjust if quantity_days < 0: quantity_days = 0 if start == end: quantity_days = 1 if Transaction().context.get('absenteeism_days'): quantity_days = quantity_days - Transaction().context.get('absenteeism_days') return quantity_days def get_salary_full(self, wage): res = super(Payroll, self).get_salary_full(wage) salary_pae = _ZERO salary_full = res['salary'] """ if wage.type_concept in ('health', 'retirement', 'fsp', 'tax', 'box_family') \ and self.kind == 'special': # PAE: Payroll Accrual Entry salary_pae = self._compute_apply_special_salary() if self.last_payroll: payrolls = self.search([ ('employee', '=', self.employee.id), ('start', '>=', date(self.start.year, 1, 1)), ('start', '<=', date(self.start.year, 12, 31)), ]) hoard_holidays = self._compute_hoard( [p.id for p in payrolls], ['holidays'] ) salary_pae += hoard_holidays """ if wage.month_application: if self._is_last_payroll_month(): salary_full_month = self.search_salary_month(wage) salary_full = salary_full_month + salary_pae elif self.last_payroll: salary_full = salary_full + salary_pae else: salary_full = _ZERO else: salary_full += salary_pae salary_full = self.validate_minimal_amount(wage, salary_full) return {'salary': salary_full} def validate_minimal_amount(self, wage, amount): if wage.minimal_amount: if amount < wage.minimal_amount: return 0 return amount def _is_last_payroll_month(self): _, end_day = calendar.monthrange(self.start.year, self.start.month) last_day = date(self.start.year, self.start.month, end_day) if last_day == self.period.end: return True return False def _compute_apply_special_salary(self): MandatoryWages = Pool().get('staff.payroll.mandatory_wage') mandatory_wages = MandatoryWages.search([ ('employee', '=', self.employee.id), ('wage_type.apply_special_salary', '=', True), ]) salary_plus = _ZERO wages = [w.wage_type for w in mandatory_wages] for wage in wages: dom_payroll = [('employee', '=', self.employee.id)] if wage.type_concept == 'bonus_service': if self.start < date(self.start.year, 7, 1): dom_payroll.extend([ ('start', '>=', date(self.start.year, 1, 1)), ('start', '<=', date(self.start.year, 6, 30)), ]) else: dom_payroll.extend([ ('start', '>=', date(self.start.year, 7, 1)), ('start', '<=', date(self.start.year, 12, 31)), ]) else: dom_payroll.extend([ ('start', '>=', date(self.start.year, 1, 1)), ('start', '<=', date(self.start.year, 12, 31)), ]) payrolls = self.search(dom_payroll) worked_days = Decimal(sum([p.worked_days for p in payrolls])) precomputed_salary = {'salary': worked_days * self.employee.salary_day} salary_plus += wage.compute_formula( 'unit_price_formula', precomputed_salary) return salary_plus def _get_payrolls_month(self): _, end_day = calendar.monthrange(self.start.year, self.start.month) start = date(self.start.year, self.start.month, 1) end = date(self.start.year, self.start.month, end_day) payrolls = self.search([ ('employee', '=', self.employee.id), ('start', '>=', start), ('start', '<=', end), ]) return payrolls def search_salary_month(self, wage): res = _ZERO Configuration = Pool().get('staff.configuration') config = Configuration(1) payrolls = self._get_payrolls_month() # payrolls_contract = {} # contract_id_payrolls = [payroll.contract.id for payroll in payrolls] if payrolls: # num_subperiod = 30 / config.default_liquidation_period # if len(payrolls) >= num_subperiod: for payroll in payrolls: res += payroll.compute_salary_full(wage) # if payroll.contract: # payrolls_contract[payroll.contract.id] = res return res def get_line(self, wage, qty, unit_value, party=None): res = super(Payroll, self).get_line(wage, qty, unit_value, party) # res['unit_value'] = self._validate_amount_wage(wage, res['unit_value']) return res # def _validate_amount_wage(self, wage, amount): # config = Pool().get('staff.configuration')(1) # salary_in_date = self.contract.get_salary_in_date(self.end) # if config and config.minimum_salary and \ # wage.type_concept == 'transport' and \ # salary_in_date >= (config.minimum_salary * 2): # amount = 0 # if wage.type_concept in SOCIAL_SEGURITY and \ # amount < wage.minimal_unit_price: # amount = wage.minimal_unit_price # return amount def set_preliquidation(self, extras, discounts=None): discounts = self.set_events() ctx = { 'absenteeism_days': self.absenteeism_days } with Transaction().set_context(ctx): super(Payroll, self).set_preliquidation(extras, discounts) self.save() self.update_wage_no_salary() def set_events(self): pool = Pool() Event = pool.get('staff.event') PayrollLine = pool.get('staff.payroll.line') events = Event.search([ ('employee', '=', self.employee), ('start_date', '<=', self.end), ('end_date', '>=', self.start), ('state', '=', 'done'), ]) days = 0 events_lines_to_create = [] absenteeism_days = 0 discounts = {} for event in events: if not event.category.payroll_effect: continue if event.absenteeism: absenteeism_days += event.days if event.quantity_pay: qty_pay = event.quantity_pay wage = event.category.wage_type salary_args = self.get_salary_full(wage) if event.amount_to_pay: unit_value = Decimal(event.amount_to_pay) else: unit_value = wage.compute_unit_price(salary_args) res = self.get_line(wage, qty_pay, unit_value) events_lines_to_create.append(res) if event.category.wage_type_discount and event.quantity_discount: id_wt_event = event.category.wage_type_discount.id if id_wt_event not in discounts.keys(): discounts[id_wt_event] = 0 discounts[id_wt_event] += event.quantity_discount self.absenteeism_days = absenteeism_days self.save() PayrollLine.create(events_lines_to_create) return discounts def update_wage_no_salary(self): PayrollLine = Pool().get('staff.payroll.line') for line in self.lines: if line.wage_type.month_application: salary_args = self.get_salary_full(line.wage_type) unit_value = line.wage_type.compute_unit_price(salary_args) if line.wage_type.type_concept == 'interest': unit_value = self._compute_interest(line.wage_type) else: continue PayrollLine.write([line], {'unit_value': unit_value}) def _get_payrolls_contract(self): dom_payroll = [('employee', '=', self.employee.id)] if not self.contract: contract = self.search_contract_on_period() else: contract = self.contract dom_payroll.append( ('start', '>=', contract.start_date), ) if contract.end_date: dom_payroll.append( ('start', '<=', contract.end_date) ) payrolls = self.search(dom_payroll) return [p.id for p in payrolls] def _compute_interest(self, wage_type, limit_date=False): start = date(self.start.year, 1, 1) end = date(self.start.year, 12, 31) concepts_salary_ids = [wt.id for wt in wage_type.concepts_salary] if self.contract.start_date > start: start = self.contract.start_date dom_payrolls = [ ('start', '>=', start), ('employee', '=', self.employee.id), ] if limit_date: dom_payrolls.append(('start', '<=', limit_date)) else: dom_payrolls.append(('start', '<=', end)) payrolls = self.search(dom_payrolls) payrolls_ids = [p.id for p in payrolls] time_contracting = sum([p.worked_days for p in payrolls]) salary_hoard = float(self._compute_hoard( payrolls_ids, concepts_salary_ids )) total_interest = salary_hoard * time_contracting * 0.01 / 360 if self.id in payrolls_ids: payrolls_ids.remove(self.id) interest_hoard = float(self._compute_hoard( payrolls_ids, [wage_type.id] )) interest = total_interest - interest_hoard if interest < _ZERO: interest = _ZERO return self.currency.round(Decimal(interest)) def _compute_hoard(self, payrolls_ids, wages_ids): if not payrolls_ids: return _ZERO PayrollLine = Pool().get('staff.payroll.line') lines = PayrollLine.search([ ('wage_type', 'in', wages_ids), ('payroll', 'in', payrolls_ids), ]) return sum([l.amount for l in lines if not l.reconciled]) def recompute_lines(self): super(Payroll, self).recompute_lines() self.update_wage_no_salary() UvtWithholding = Pool().get('staff.payroll.uvt_withholding') line_tax = None deductions = _ZERO for line in self.lines: if line.wage_type.definition == 'deduction': deductions += line.amount if line.wage_type.type_concept == 'tax': line_tax = line if line_tax: salary_full = self.get_salary_full(line_tax.wage_type) deductions_month = self.get_deductions_month() salary_full = salary_full['salary'] payrolls_ids = self._get_payrolls_contract() if self.last_payroll: hoard_holidays = self._compute_hoard( payrolls_ids, ['holidays'] ) salary_full += hoard_holidays base_salary_withholding = salary_full - deductions_month unit_value = UvtWithholding.compute_withholding( base_salary_withholding) unit_value = self.currency.round(Decimal(unit_value)) line_tax.write([line_tax], {'unit_value': unit_value}) def get_non_fiscal_amount(self, name=None): res = _ZERO concept = name[:-7] for line in self.lines: if line.wage_type.type_concept == concept: res += line.amount return res def get_deductions_month(self): payrolls = self._get_payrolls_month() sum_deductions = _ZERO for payroll in payrolls: for line in payroll.lines: if line.wage_type.definition == 'deduction': sum_deductions += line.amount return sum_deductions @fields.depends('lines') def on_change_with_ibc(self, name=None): concepts = ['salary', 'transport', 'extras', 'bonus', 'commission', 'bonus_service', 'advance'] res = _ZERO for l in self.lines: if l.wage_type and l.wage_type.type_concept in concepts and \ l.wage_type.definition == 'payment' and \ l.wage_type.salary_constitute == True: res += l.amount return res @classmethod @Workflow.transition('draft') def draft(cls, records): Move = Pool().get('account.move') for payroll in records: if payroll.move: Move.draft([payroll.move.id]) #Move.delete([payroll.move]) class PayrollGlobalStart(ModelView): 'Payroll Global Start' __name__ = 'staff.payroll_global.start' start_period = fields.Many2One('staff.payroll.period', 'Start Period', required=True) end_period = fields.Many2One('staff.payroll.period', 'End Period', depends=['start_period']) company = fields.Many2One('company.company', 'Company', required=True) department = fields.Many2One('company.department', 'Department') include_finished = fields.Boolean('Include Finished Contract') @staticmethod def default_company(): return Transaction().context.get('company') @fields.depends('start_period') def on_change_with_end_period(self, name=None): if self.start_period: return self.start_period.id class PayrollGlobal(Wizard): 'Payroll Global' __name__ = 'staff.payroll.global' start = StateView('staff.payroll_global.start', 'staff_payroll_co.payroll_global_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Print', 'print_', 'tryton-ok', default=True), ]) print_ = StateReport('staff.payroll.global_report') def do_print_(self, action): end_period_id = None department_id = None if self.start.end_period: end_period_id = self.start.end_period.id if self.start.department: department_id = self.start.department.id data = { 'company': self.start.company.id, 'start_period': self.start.start_period.id, 'end_period': end_period_id, 'department': department_id, 'include_finished': self.start.include_finished, } return action, data def transition_print_(self): return 'end' class PayrollGlobalReport(Report): __name__ = 'staff.payroll.global_report' @classmethod def get_domain_payroll(cls, data): dom_payroll = [] return dom_payroll @classmethod def get_context(cls, records, data): report_context = super(PayrollGlobalReport, cls).get_context(records, data) pool = Pool() user = pool.get('res.user')(Transaction().user) Payroll = pool.get('staff.payroll') PayrollLine = pool.get('staff.payroll.line') Period = pool.get('staff.payroll.period') Department = pool.get('company.department') start_period = Period(data['start_period']) dom_periods = [] if data['end_period']: end_period = Period(data['end_period']) dom_periods.extend([ ('start', '>=', start_period.start), ('end', '<=', end_period.end), ]) else: dom_periods.append( ('id', '=', start_period.id) ) periods = Period.search(dom_periods) dom_pay = cls.get_domain_payroll(data) dom_pay.append( ('period', 'in', [p.id for p in periods]), ) dom_pay.append( ('state', 'in', ['processed', 'posted', 'draft']), ) if data['department']: dom_pay.append( ['AND', ['OR', [ ('employee.department', '=', data['department']), ('department', '=', None), ],[ ('department', '=', data['department']), ], ]] ) department = Department(data['department']).name else: department = None if not data['include_finished']: dom_pay.append( ('contract.state', '!=', 'finished') ) payrolls = Payroll.search(dom_pay) periods_number = len(periods) default_vals = cls.default_values() sum_gross_payments = [] sum_total_deductions = [] sum_net_payment = [] parties = {} payments = ['salary', 'transport', 'extras', 'food', 'bonus'] deductions = ['health', 'retirement', 'tax', 'syndicate', 'fsp', 'acquired_product'] for payroll in payrolls: employee_id = payroll.employee.id party_health = '' party_retirement = '' if employee_id not in parties.keys(): position_employee = payroll.employee.position.name if payroll.employee.position else '' position_contract = payroll.contract.position.name if payroll.contract and payroll.contract.position else '' parties[employee_id] = default_vals.copy() parties[employee_id]['employee_code'] = payroll.employee.code parties[employee_id]['employee'] = payroll.employee.party.name parties[employee_id]['employee_id_number'] = payroll.employee.party.id_number if payroll.employee.party_health: party_health = payroll.employee.party_health.name if payroll.employee.party_retirement: party_retirement = payroll.employee.party_retirement.name parties[employee_id]['party_health'] = party_health parties[employee_id]['party_retirement'] = party_retirement # parties[employee_id]['employee_salary'] = payroll.employee.salary parties[employee_id]['basic_salary'] = payroll.contract.get_salary_in_date(payroll.end) # payroll_lines = PayrollLine.search([ # ('payroll', '=', payroll.id), # ('wage_type.type_concept', '=', 'salary'), # ]) # if payroll_lines: # basic_salary = math.ceil(payroll_lines[0].unit_value * 240) # parties[employee_id]['basic_salary'] = cls.round_basic_salary(basic_salary) # else: # parties[employee_id]['basic_salary'] = payroll.contract.salary parties[employee_id]['employee_position'] = position_contract or position_employee or '' for line in payroll.lines: if line.wage_type.type_concept in (payments + deductions): concept = line.wage_type.type_concept else: if line.wage_type.type_concept == 'commission': concept = 'commission' elif line.wage_type.type_concept == 'loan': concept = 'loan' elif line.wage_type.type_concept == 'advance': concept = 'advance' elif line.wage_type.definition == 'payment' and line.wage_type.receipt: concept = 'others_payments' elif line.wage_type.definition == 'deduction' or \ line.wage_type.definition == 'discount' and \ line.wage_type.receipt: concept = 'others_deductions' else: concept = line.wage_type.type_concept parties[employee_id][concept] += line.amount parties[employee_id]['worked_days'] += payroll.worked_days parties[employee_id]['gross_payments'] += payroll.gross_payments parties[employee_id]['total_deductions'] += payroll.total_deductions parties[employee_id]['net_payment'] += payroll.net_payment sum_gross_payments.append(payroll.gross_payments) sum_total_deductions.append(payroll.total_deductions) sum_net_payment.append(payroll.net_payment) employee_dict = {e['employee']: e for e in parties.values()} report_context['records'] = sorted(employee_dict.items(), key=lambda t: t[0]) report_context['department'] = department report_context['periods_number'] = periods_number report_context['start_period'] = start_period report_context['end_period'] = end_period report_context['company'] = user.company report_context['user'] = user report_context['sum_gross_payments'] = sum(sum_gross_payments) report_context['sum_net_payment'] = sum(sum_net_payment) report_context['sum_total_deductions'] = sum(sum_total_deductions) return report_context @classmethod def default_values(cls): WageType = Pool().get('staff.wage_type') fields_string = ['employee_code', 'employee', 'employee_salary', 'employee_id_number', 'party_health', 'party_retirement', 'basic_salary', ] fields_numeric = ['net_payment', 'worked_days', 'gross_payments', 'total_deductions', 'others_payments', 'others_deductions'] lines_fields = dict(WageType.type_concept.selection).keys() if '' in lines_fields: lines_fields.remove('') default_values = {} for field in fields_string: default_values.setdefault(field, None) for field in fields_numeric: default_values.setdefault(field, Decimal(0)) for field in lines_fields: default_values.setdefault(field, Decimal(0)) return default_values # @classmethod # def round_basic_salary(cls, salary): # basic_salary = math.ceil(salary) # string_basic_salary = str(basic_salary)[len(str(basic_salary))-1] # if string_basic_salary == '9': # return basic_salary + 1 # elif string_basic_salary == '1': # return basic_salary - 1 # else: # return basic_salary class PayrollPaymentStart(ModelView): 'Payroll Payment Start' __name__ = 'staff.payroll_payment.start' period = fields.Many2One('staff.payroll.period', 'Period', required=True) company = fields.Many2One('company.company', 'Company', required=True) department = fields.Many2One('company.department', 'Department') @staticmethod def default_company(): return Transaction().context.get('company') class PayrollPayment(Wizard): 'Payroll Payment' __name__ = 'staff.payroll.payment' start = StateView('staff.payroll_payment.start', 'staff_payroll_co.payroll_payment_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Print', 'print_', 'tryton-ok', default=True), ]) print_ = StateReport('staff.payroll.payment_report') def do_print_(self, action): period = None department_id = None if self.start.department: department_id = self.start.department.id if self.start.period: period = self.start.period.id data = { 'company': self.start.company.id, 'period': period, 'department': department_id, } return action, data def transition_print_(self): return 'end' class PayrollPaymentReport(Report): __name__ = 'staff.payroll.payment_report' @classmethod def get_context(cls, records, data): report_context = super(PayrollPaymentReport, cls).get_context(records, data) user = Pool().get('res.user')(Transaction().user) Payroll = Pool().get('staff.payroll') Period = Pool().get('staff.payroll.period') Department = Pool().get('company.department') clause = [] start_date = None end_date = None period = None period_name = None department = None if data['period']: period = Period(data['period']) start_date = period.start end_date = period.end clause = [('period', '=', period)] period_name = period.name if data['department']: clause.append(('employee.department', '=', data['department'])) department = Department(data['department']).name payrolls = Payroll.search(clause) new_objects = [] sum_net_payment = [] values = {} for payroll in payrolls: values = values.copy() values['employee'] = payroll.employee.party.name values['employee_id_number'] = payroll.employee.party.id_number values['bank_name'] = payroll.employee.party.bank_name values['bank_account'] = payroll.employee.party.bank_account values['type_account'] = payroll.employee.party.bank_account_type values['net_payment'] = payroll.net_payment sum_net_payment.append(payroll.net_payment) new_objects.append(values) report_context['records'] = new_objects report_context['department'] = department report_context['start_date'] = start_date report_context['end_date'] = end_date report_context['period'] = period_name report_context['company'] = user.company report_context['user'] = user report_context['sum_net_payment'] = sum(sum_net_payment) return report_context class PayrollPaycheckStart(ModelView): 'Payroll Paycheck Start' __name__ = 'staff.payroll_paycheck.start' periods = fields.One2Many('staff.payroll.period', None, 'Periods', add_remove=[], required=True) company = fields.Many2One('company.company', 'Company', required=True) department = fields.Many2One('company.department', 'Department') values_without_move = fields.Boolean('Values Without Move') @staticmethod def default_company(): return Transaction().context.get('company') class PayrollPaycheck(Wizard): 'Payroll Paycheck' __name__ = 'staff.payroll.paycheck' start = StateView('staff.payroll_paycheck.start', 'staff_payroll_co.payroll_paycheck_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Print', 'print_', 'tryton-ok', default=True), ]) print_ = StateReport('staff.payroll.paycheck_report') def do_print_(self, action): department_id = None if self.start.department: department_id = self.start.department.id periods = [p.id for p in self.start.periods] data = { 'company': self.start.company.id, 'periods': periods, 'department': department_id, 'values_without_move': self.start.values_without_move, } return action, data def transition_print_(self): return 'end' class PayrollPaycheckReport(Report): __name__ = 'staff.payroll.paycheck_report' @classmethod def get_domain_payroll(cls, data): dom_payroll = [ ('period', 'in', data['periods']), ('state', '!=', 'draft'), ] if data['department']: dom_payroll.append( ('employee.department', '=', data['department']) ) return dom_payroll @classmethod def get_context(cls, records, data): report_context = super(PayrollPaycheckReport, cls).get_context(records, data) pool = Pool() Payroll = pool.get('staff.payroll') Period = pool.get('staff.payroll.period') Company = pool.get('company.company') dom_payroll = cls.get_domain_payroll(data) payrolls = Payroll.search(dom_payroll) res = {} today = date.today() periods = [p.name for p in Period.browse(data['periods'])] total = [] for payroll in payrolls: employee_id = payroll.employee.id health_amount = cls.get_amount_move(payroll, 'health') retirement_amount = cls.get_amount_move(payroll, 'retirement') risk_amount = cls.get_amount_move(payroll, 'risk') box_family_amount = cls.get_amount_move(payroll, 'box_family') if data['values_without_move']: health_amount = cls._values_without_move(payroll, 'health') retirement_amount = cls._values_without_move(payroll, 'retirement') risk_amount = cls._values_without_move(payroll, 'risk') box_family_amount = cls._values_without_move(payroll, 'box_family') subtotal = sum([health_amount, retirement_amount, risk_amount, box_family_amount]) if employee_id not in res.keys(): eps_code = afp_code = arl_code = box_code = '' eps_name = afp_name = arl_name = box_name = '' if payroll.employee.party_health: eps_code = payroll.employee.party_health.legal_code eps_name = payroll.employee.party_health.name if payroll.employee.party_retirement: afp_code = payroll.employee.party_retirement.legal_code afp_name = payroll.employee.party_retirement.name if payroll.employee.party_risk: arl_code = payroll.employee.party_risk.legal_code arl_name = payroll.employee.party_risk.name if payroll.employee.party_box_family: box_code = payroll.employee.party_box_family.legal_code box_name = payroll.employee.party_box_family.name res[employee_id] = { 'type_contributor': '01', 'type_id': 'CC', 'id_number': payroll.employee.party.id_number, 'employee': payroll.employee.party.name, 'type_affiliation': 'D', 'year': payroll.date_effective.year, 'date': today, 'eps_code': eps_code, 'eps_name': eps_name, 'afp_code': afp_code, 'afp_name': afp_name, 'arl_code': arl_code, 'arl_name': arl_name, 'box_code': box_code, 'box_name': box_name, 'ibc': payroll.ibc, 'eps_amount': health_amount, 'afp_amount': retirement_amount, 'arl_amount': risk_amount, 'box_amount': box_family_amount, 'subtotal': subtotal } else: res[employee_id]['eps_amount'] += health_amount res[employee_id]['afp_amount'] += retirement_amount res[employee_id]['arl_amount'] += risk_amount res[employee_id]['box_amount'] += box_family_amount res[employee_id]['ibc'] += payroll.ibc res[employee_id]['subtotal'] += subtotal total.append(subtotal) report_context['total'] = sum(total) report_context['records'] = res.values() report_context['periods'] = periods report_context['company'] = Company(data['company']) return report_context @classmethod def _values_without_move(cls, payroll, kind): res = 0 for line in payroll.lines: if line.wage_type.type_concept == kind: res += line.amount # if kind == 'retirement': # res += Decimal((float(line.amount) * 0.16) / 0.04) # else: # res += line.amount return res @classmethod def get_amount_move(cls, payroll, kind): res = _ZERO party = None if kind == 'health': party = payroll.employee.party_health elif kind == 'retirement': party = payroll.employee.party_retirement elif kind == 'risk': party = payroll.employee.party_risk else: party = payroll.employee.party_box_family if party and payroll.move: for line in payroll.move.lines: if line.party.id == party.id: res = line.credit break return res class PayrollSheetStart(ModelView): 'Payroll Sheet Start' __name__ = 'staff.payroll.sheet.start' periods = fields.One2Many('staff.payroll.period', None, 'Periods', add_remove=[], required=True) company = fields.Many2One('company.company', 'Company', required=True) @staticmethod def default_company(): return Transaction().context.get('company') class PayrollSheet(Wizard): 'Payroll Sheet' __name__ = 'staff.payroll.sheet' start = StateView('staff.payroll.sheet.start', 'staff_payroll_co.payroll_sheet_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Print', 'print_', 'tryton-ok', default=True), ]) print_ = StateReport('staff.payroll.sheet_report') def do_print_(self, action): periods = [p.id for p in self.start.periods] data = { 'company': self.start.company.id, 'periods': periods, } return action, data def transition_print_(self): return 'end' class PayrollSheetReport(Report): __name__ = 'staff.payroll.sheet_report' @classmethod def get_domain_payroll(cls, data): dom_payroll = [] return dom_payroll @classmethod def get_context(cls, records, data): report_context = super(PayrollSheetReport, cls).get_context(records, data) pool = Pool() user = pool.get('res.user')(Transaction().user) Payroll = pool.get('staff.payroll') PayrollLine = pool.get('staff.payroll.line') Period = pool.get('staff.payroll.period') clause = [] periods_names = '' dom_payroll = cls.get_domain_payroll(data) if data['periods']: periods = Period.browse(data['periods']) periods_names = [p.name + ' / ' for p in periods] dom_payroll.append([('period', 'in', data['periods'])]) payrolls = Payroll.search(dom_payroll, order=[('employee.party.name', 'ASC'), ('period.name', 'ASC')]) new_objects = [] default_vals = cls.default_values() sum_gross_payments = [] sum_total_deductions = [] sum_net_payment = [] item = 0 for payroll in payrolls: item += 1 values = default_vals.copy() values['item'] = item values['employee'] = payroll.employee.party.name values['id_number'] = payroll.employee.party.id_number position_name, position_contract = '', '' if payroll.employee.position: position_name = payroll.employee.position.name if payroll.contract and payroll.contract.position: position_contract = payroll.contract.position.name values['position'] = position_contract or position_name values['department'] = payroll.employee.department.name \ if payroll.employee.department else '' values['company'] = user.company.party.name values['legal_salary'] = payroll.contract.get_salary_in_date( payroll.end) values['period'] = payroll.period.name salary_day_in_date = payroll.contract.get_salary_in_date( payroll.end)/30 values['salary_day'] = salary_day_in_date values['salary_hour'] = (salary_day_in_date / 8) if salary_day_in_date else 0 # values['salary_day'] = payroll.employee.salary_day # values['salary_hour'] = (payroll.employee.salary_day / 8) values['worked_days'] = payroll.worked_days values['gross_payment'] = payroll.gross_payments # Add compatibility with staff contracting project = "" if hasattr(payroll, 'project'): if payroll.project: project = payroll.project.name if hasattr(payroll.employee, 'project_contract'): if payroll.employee.project_contract and \ payroll.employee.project_contract.reference: project = payroll.employee.project_contract.reference values['project'] = project values.update(cls._prepare_lines(payroll, values)) sum_gross_payments.append(payroll.gross_payments) sum_total_deductions.append(payroll.total_deductions) sum_net_payment.append(payroll.net_payment) new_objects.append(values) report_context['records'] = new_objects report_context['periods'] = periods_names report_context['company'] = user.company report_context['user'] = user report_context['sum_gross_payments'] = sum(sum_gross_payments) report_context['sum_net_payment'] = sum(sum_net_payment) report_context['sum_total_deductions'] = sum(sum_total_deductions) return report_context @classmethod def default_values(cls): fields_no_amount = [ 'item', 'employee', 'id_number', 'position', 'legal_salary', 'salary_day', 'salary_hour', 'worked_days', 'period', 'department', ] fields_amount = [ 'salary', 'reco', 'recf', 'hedo', 'heno', 'dom', 'hedf', 'henf', 'cost_reco', 'cost_recf', 'cost_hedo', 'cost_heno', 'cost_dom', 'cost_hedf', 'cost_henf', 'bonus', 'total_extras', 'gross_payment', 'health', 'retirement', 'food', 'transport', 'fsp', 'retefuente', 'other_deduction', 'total_deduction', 'ibc', 'net_payment', 'box_family', 'box_family', 'unemployment', 'interest', 'holidays', 'bonus_service', 'discount', 'other', 'total_benefit', 'risk', 'health_provision', 'retirement_provision', 'total_ssi', 'total_cost', 'sena', 'icbf', 'acquired_product', ] if '' in fields_no_amount: fields_no_amount.remove('') default_values = {} for field in fields_no_amount: default_values.setdefault(field, None) for field in fields_amount: default_values.setdefault(field, Decimal(0)) return default_values @classmethod def _prepare_lines(cls, payroll, vals): extras =[ 'reco', 'recf', 'hedo', 'heno', 'dom', 'hedf', 'henf', ] for line in payroll.lines: if line.wage_type.definition == 'payment': if line.wage_type.type_concept == 'salary': vals['salary'] += line.amount elif line.wage_type.type_concept == 'extras': vals['total_extras'] += line.amount for e in extras: if e.upper() in line.wage_type.name: vals[e] += line.quantity vals['cost_' + e] += line.amount break elif line.wage_type.type_concept == 'risk': vals['risk'] += line.amount elif line.wage_type.type_concept == 'box_family': vals['box_family'] += line.amount elif line.wage_type.type_concept == 'unemployment': vals['unemployment'] += line.amount elif line.wage_type.type_concept == 'interest': vals['interest'] += line.amount elif line.wage_type.type_concept == 'holidays': vals['holidays'] += line.amount elif line.wage_type.type_concept == 'bonus': vals['bonus'] += line.amount elif line.wage_type.type_concept == 'bonus_service': vals['bonus_service'] += line.amount elif line.wage_type.type_concept == 'transport': vals['transport'] += line.amount elif line.wage_type.type_concept == 'food': vals['food'] += line.amount elif line.wage_type.type_concept == 'sena': vals['sena'] += line.amount elif line.wage_type.type_concept == 'icbf': vals['icbf'] += line.amount elif line.wage_type.type_concept == 'acquired_product': vals['acquired_product'] += line.amount else: print('Warning: Line no processed... ', line.wage_type.name) vals['other'] += line.amount elif line.wage_type.definition == 'deduction': vals['total_deduction'] += line.amount if line.wage_type.type_concept == 'health': vals['health'] += line.amount vals['health_provision'] += line.get_expense_amount() elif line.wage_type.type_concept == 'retirement': vals['retirement'] += line.amount vals['retirement_provision'] += line.get_expense_amount() else: if line.wage_type.type_concept == 'fsp': vals['fsp'] += line.amount elif line.wage_type.type_concept == 'tax': vals['retefuente'] += line.amount else: vals['other_deduction'] += line.amount else: vals['discount'] += line.amount print('Warning: Line no processed... ', line.wage_type.name) vals['gross_payment'] = vals['salary'] + vals['total_extras'] + vals['transport'] + vals['food'] + vals['bonus'] vals['net_payment'] = vals['gross_payment'] - vals['total_deduction'] vals['ibc'] = vals['gross_payment'] vals['total_benefit'] = vals['unemployment'] + vals['interest'] + vals['holidays'] + vals['bonus_service'] vals['total_ssi'] = vals['retirement_provision'] + vals['risk'] vals['total_cost'] = vals['total_ssi'] + vals['box_family'] + vals['net_payment'] + vals['total_benefit'] return vals class PayrollGroupStart: __metaclass__ = PoolMeta __name__ = 'staff.payroll_group.start' department = fields.Many2One('company.department', 'Department') class PayrollGroup: __metaclass__ = PoolMeta __name__ = 'staff.payroll_group' def get_employees_dom(self, employees_w_payroll): dom_employees = super(PayrollGroup, self).get_employees_dom(employees_w_payroll) if self.start.department: dom_employees.append( ('department', '=', self.start.department.id), ) return dom_employees # def contract_out_date(self, employee): # if employee.contract.end_date and \ # employee.contract.end_date < self.start.period.start: # return True # return False class OpenPayrollByPeriodStart(ModelView): 'Open Payroll By Period Start' __name__ = 'staff_payroll_co.open_payroll_by_period.start' company = fields.Many2One('company.company', 'Company', required=True) fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year', required=True) @staticmethod def default_company(): return Transaction().context.get('company') @staticmethod def default_fiscalyear(): FiscalYear = Pool().get('account.fiscalyear') return FiscalYear.find( Transaction().context.get('company'), exception=False) class OpenPayrollByPeriod(Wizard): 'Open Payroll By Period' __name__ = 'staff_payroll_co.open_payroll_by_period' start = StateView('staff_payroll_co.open_payroll_by_period.start', 'staff_payroll_co.open_payroll_by_period_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Print', 'open_', 'tryton-ok', default=True), ]) open_ = StateAction('staff_payroll_co.act_payroll_by_period_board') def do_open_(self, action): data = { 'company': self.start.company.id, 'fiscalyear': self.start.fiscalyear.id, } action['name'] += ' - %s' % self.start.fiscalyear.name return action, data def transition_open_(self): return 'end' class PayrollByPeriodDynamic(Period): 'Payroll By Period Dynamic' __name__ = 'staff_payroll_co.payroll_by_period_dynamic' payrolls = fields.Function(fields.One2Many('staff.payroll', None, 'Payrolls'), 'get_payrolls') amount_net_payment = fields.Function(fields.Numeric('Amount Net Payment', digits=(16, 2)), 'get_amount') amount_total_cost = fields.Function(fields.Numeric('Amount Total Cost', digits=(16, 2)), 'get_amount') def get_amount(self, name=None): res = [] method = name[7:] for payroll in self.payrolls: value = getattr(payroll, method) res.append(value) return sum(res) def get_payrolls(self, name=None): pool = Pool() Payroll = pool.get('staff.payroll') payrolls = Payroll.search([ ('period', '=', self.id), ('state', 'in', ['processed', 'posted']), ]) return [i.id for i in payrolls] class FixPayrollStart(ModelView): 'Fix Payroll Start' __name__ = 'staff.fix_payroll.start' start_period = fields.Many2One('staff.payroll.period', 'Start Period', required=True) end_period = fields.Many2One('staff.payroll.period', 'End Period', depends=['start_period'], required=True) employee = fields.Many2One('company.employee', 'Employee') wage_type = fields.Many2One('staff.wage_type', 'Wage Type', required=True) project = fields.Many2One('project.work', 'Project', domain=[ ('type', '=', 'project') ]) class FixPayroll(Wizard): 'Fix Payroll' __name__ = 'staff.fix_payroll' start = StateView('staff.fix_payroll.start', 'staff_payroll_co.fix_payroll_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Accept', 'open_', 'tryton-ok', default=True), ]) open_ = StateTransition() def transition_open_(self): pool = Pool() Payroll = pool.get('staff.payroll') Move = pool.get('account.move') Employee = pool.get('company.employee') dom_start = [ ('end', '=', '2018-12-31'), ('project', '=', self.start.project.id) ] if self.start.employee: dom_start.append(('employee', '=', self.start.employee.id)) payrolls = Payroll.search(dom_start) for payroll in payrolls: dom_payrolls = [ ('start', '>=', self.start.start_period.start), ('start', '<=', self.start.end_period.start), ('state', '=', 'posted'), ('employee', '=', payroll.employee.id), ('contract', '=', payroll.contract.id), ] _payrolls = Payroll.search(dom_payrolls, order=[('start', 'ASC')]) move_ids = [ p.move.id for p in _payrolls] Move.draft(move_ids) amount = 0 for _payroll in _payrolls: if not _payroll.move: continue # _payroll.move.draft([_payroll.move.id]) # _payroll.move.save() for line in _payroll.lines: if line.wage_type.id == self.start.wage_type.id: self.recompute_wage_type(line) # _payroll.save() # amount = line.amount # acc_debit_id = line.wage_type.debit_account.id # acc_credit_id = line.wage_type.credit_account.id break debit_ready = credit_ready = False # for mline in _payroll.move.lines: # if debit_ready and credit_ready: # break # if mline.reconciliation: # continue # # if mline.account.id in [acc_debit_id, acc_credit_id]: # # if mline.account.id == acc_credit_id: # # mline.write([mline], {'credit': amount}) # # credit_ready = True # # elif mline.account.id == acc_debit_id: # # mline.write([mline], {'debit': amount}) # # acc_debit_id = True # payroll.process([payroll]) # payroll.post([payroll]) # if _payroll.move.state == 'posted': # continue # if debit_ready and credit_ready: # _payroll.move.write([payroll.move], { # 'state': 'posted', # }) # if payroll.move and line.wage_type.type_concept in ['risk', 'box_family']: # self.fix_move(payroll.move, line.wage_type, line.amount) return 'end' def _compute_wage_type(self, wage_type, payroll): salary_args = payroll.get_salary_full(wage_type) return wage_type.compute_unit_price(salary_args) def recompute_wage_type(self, pline): Line = Pool().get('account.move.line') wage_type = pline.wage_type unit_value = None salary_args = {} salary = [] if wage_type.type_concept in ['risk', 'box_family']: unit_value = self._compute_wage_type(wage_type, pline.payroll) if wage_type.type_concept == 'interest': unit_value = pline.payroll._compute_interest(pline.wage_type, pline.payroll.start) elif wage_type.type_concept == 'holidays': concepts_salary_ids = [cs.id for cs in wage_type.concepts_salary] for line in pline.payroll.lines: if line.wage_type.id in concepts_salary_ids: salary.append(line.amount) salary_args['salary'] = sum(salary) unit_value = wage_type.compute_unit_price(salary_args) if not unit_value: return pline.write([pline], {'unit_value': unit_value}) pline.on_change_with_amount() if pline.payroll.move: # Check that line is not reconciled move = pline.payroll.move debit_account = wage_type.debit_account.id credit_account = wage_type.credit_account.id debit_ready = False credit_ready = False vals = [] to_process = [l for l in move.lines if l.account.id in (debit_account, credit_account)] for line in to_process: if line.reconciliation or len(to_process) == 1: return # if len(to_process) > 1: for line in to_process: # if debit_ready and credit_ready: # break # if line.reconciliation: # break if line.account.id == debit_account and line.debit != pline.amount: Line.write([line], {'debit': pline.amount}) elif line.account.id == credit_account and line.credit != pline.amount: Line.write([line], {'credit': pline.amount}) # vals[line] = {} # if line.debit > 0: # vals = {'debit': pline.amount} # debit_ready = True # else: # vals = {'credit': pline.amount} # # vals = {'credit': pline.amount} # credit_ready = True # move.write([move], {'state': 'draft'}) # if len(vals) == 2: # for l, v in vals: move.write([move], { 'state': 'posted', }) def fix_move(self, move, wage_type, amount): accounts = [] if wage_type.credit_account: accounts.append(wage_type.credit_account.id) if wage_type.debit_account: accounts.append(wage_type.debit_account.id) move.write([move], { 'state': 'draft', }) for line in move.lines: if line.account.id not in accounts: continue if line.debit > 0: line.debit = amount else: line.credit = amount line.save() move.write([move], { 'state': 'posted', }) class Exo2276Start(ModelView): 'Payroll Exo 2276 Start' __name__ = 'staff.payroll_exo2276.start' start_period = fields.Many2One('staff.payroll.period', 'Start Period', required=True) end_period = fields.Many2One('staff.payroll.period', 'End Period', required=True) company = fields.Many2One('company.company', 'Company', required=True) @staticmethod def default_company(): return Transaction().context.get('company') class Exo2276(Wizard): 'Payroll Exo 2276' __name__ = 'staff.payroll_exo2276' start = StateView('staff.payroll_exo2276.start', 'staff_payroll_co.payroll_exo2276_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Print', 'print_', 'tryton-ok', default=True), ]) print_ = StateReport('staff.payroll_exo2276.report') def do_print_(self, action): data = { 'company': self.start.company.id, 'start_period': self.start.start_period.start, 'end_period': self.start.end_period.end, } return action, data def transition_print_(self): return 'end' class Exo2276Report(Report): __name__ = 'staff.payroll_exo2276.report' @classmethod def get_domain_payroll(cls, data=None): Period = Pool().get('staff.payroll.period') periods = Period.search([ ('start', '>=', data['start_period']), ('end', '<=', data['end_period']), ]) periods_ids = [p.id for p in periods] domain = [ ('period', 'in', periods_ids), ('state', '=', 'posted'), ] return domain @classmethod def get_context(cls, records, data): report_context = super(Exo2276Report, cls).get_context(records, data) pool = Pool() user = pool.get('res.user')(Transaction().user) Payroll = pool.get('staff.payroll') LiquidationLine = pool.get('staff.liquidation.line') domain_ = cls.get_domain_payroll(data) payrolls = Payroll.search([domain_]) new_objects = {} index = 0 for payroll in payrolls: index += 1 party = payroll.employee.party if party.id in new_objects.keys(): continue new_objects[party.id] = { 'index': index, 'type_document': party.type_document, 'id_number': party.id_number, 'first_family_name': party.first_family_name, 'second_family_name':party.second_family_name, 'first_name': party.first_name, 'second_name':party.second_name, 'addresses': party.addresses[0].street, 'department_code': party.department_code, 'city_code': party.city_code, 'country_code': party.country_code, 'email': party.email, 'payments': 0, 'cesanpag': 0, 'interest': 0, 'holidays': 0, 'bonus': 0, 'bonus_service': 0, 'transport': 0, 'food': 0, 'total_deduction': 0, 'health': 0, 'retirement': 0, 'fsp': 0, 'retefuente': 0, 'other_deduction': 0, 'discount': 0, 'other': 0, 'various': 0, 'salary':0, 'extras':0, 'box_family':0, 'risk':0, 'unemployment':0, 'allowance': 0, 'syndicate': 0, 'commission': 0, 'total_benefit':0, 'gross_payment':0, '_cesanpag':0, 'others_payments':0, 'total_retirement':0, 'total_salary':0, } new_objects[party.id] = cls._prepare_lines(payrolls, new_objects[party.id], party.id) _cesanpag = 0 lines_liquid = LiquidationLine.search([ ('liquidation.employee.party', '=', party.id), ('liquidation.state', '=', 'posted'), ('liquidation.liquidation_date', '>=', data['start_period']), ('liquidation.liquidation_date', '<=', data['end_period']), ('wage.type_concept', 'in', ['unemployment', 'interest']), ]) if lines_liquid: _cesanpag = sum([l.amount for l in lines_liquid]) new_objects[party.id]['cesanpag'] = _cesanpag report_context['records'] = new_objects.values() report_context['today'] = date.today() report_context['company'] = user.company return report_context @classmethod def _prepare_lines(cls, payrolls, vals, party_id): payroll_ids = [payroll.id for payroll in payrolls] Lines = Pool().get('staff.payroll.line') lines = Lines.search([ ('payroll', 'in', payroll_ids), ('payroll.employee.party', '=', party_id), ]) for line in lines: if line.wage_type.definition == 'payment': vals['payments'] += line.amount if line.wage_type.type_concept == 'unemployment': continue # vals['unemployment'] += line.amount elif line.wage_type.type_concept == 'interest': continue # vals['interest'] += line.amount elif line.wage_type.type_concept == 'salary': vals['salary'] += line.amount elif line.wage_type.type_concept == 'commission': vals['payments'] += line.amount vals['commission'] += line.amount elif line.wage_type.type_concept == 'allowance': vals['payments'] += line.amount vals['allowance'] += line.amount elif line.wage_type.type_concept == 'extras': vals['payments'] += line.amount vals['extras'] += line.amount elif line.wage_type.type_concept == 'holidays': vals['holidays'] += line.amount elif line.wage_type.type_concept == 'bonus': vals['payments'] += line.amount vals['bonus'] += line.amount elif line.wage_type.type_concept == 'bonus_service': vals['payments'] += line.amount vals['bonus_service'] += line.amount elif line.wage_type.type_concept == 'transport': vals['payments'] += line.amount vals['transport'] += line.amount elif line.wage_type.type_concept == 'food': vals['payments'] += line.amount vals['food'] += line.amount elif line.wage_type.type_concept == 'box_family': vals['box_family'] += line.amount elif line.wage_type.type_concept == 'risk': vals['risk'] += line.amount elif line.wage_type.type_concept == 'other': vals['other'] += line.amount else: print('Warning: Line no processed... ', line.wage_type.type_concept, line.wage_type.name) vals['various'] += line.amount elif line.wage_type.definition == 'deduction': vals['total_deduction'] += line.amount if line.wage_type.type_concept == 'health': vals['health'] += line.amount elif line.wage_type.type_concept == 'retirement': vals['retirement'] += line.amount else: if line.wage_type.type_concept == 'fsp': vals['fsp'] += line.amount elif line.wage_type.type_concept == 'tax': vals['retefuente'] += line.amount else: vals['other_deduction'] += line.amount else: vals['discount'] += line.amount print('Warning: Line no processed... ', line.wage_type.name) # vals['cesanpag'] = vals['unemployment'] + vals['interest'] vals['others_payments'] = (vals['other'] + vals['commission'] + vals['bonus'] + vals['allowance'] + vals['various'] + vals['food']) vals['total_benefit'] = vals['holidays'] + vals['bonus_service'] vals['total_retirement'] = vals['fsp'] + vals['retirement'] vals['total_salary'] = vals['salary'] + vals['extras'] + vals['transport'] return vals class IncomeWithholdingsStart(ModelView): 'Income Withholding Start' __name__ = 'staff.payroll.income_withholdings.start' start_period = fields.Many2One('staff.payroll.period', 'Start Period', required=True) end_period = fields.Many2One('staff.payroll.period', 'End Period', required=True) company = fields.Many2One('company.company', 'Company', required=True) employees = fields.Many2Many('company.employee', None, None, 'Employees') @staticmethod def default_company(): return Transaction().context.get('company') class IncomeWithholdings(Wizard): 'Income Withholding' __name__ = 'staff.payroll.income_withholdings' start = StateView('staff.payroll.income_withholdings.start', 'staff_payroll_co.income_withholdings_start_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Print', 'print_', 'tryton-ok', default=True), ]) print_ = StateReport('staff.payroll.income_withholdings_report') def do_print_(self, action): employees = [] if self.start.employees: employees = [e.id for e in self.start.employees] data = { 'company': self.start.company.id, 'start_period': self.start.start_period.start, 'end_period': self.start.end_period.end, 'employees': employees, } return action, data def transition_print_(self): return 'end' class IncomeWithholdingsReport(Exo2276Report): 'Income Withholding Report' __name__ = 'staff.payroll.income_withholdings_report' @classmethod def get_domain_payroll(cls, data=None): domain_ = super(IncomeWithholdingsReport, cls).get_domain_payroll(data) if data['employees']: domain_.append(('employee', 'in', data['employees'])) return domain_ class ExportMovesReport(PayrollReport): __name__ = 'staff_payroll.export_moves.report' @classmethod def get_context(cls, records, data): report_context = super(ExportMovesReport, cls).get_context(records, data) config = Pool().get('staff.configuration')(1) prefix = '' if config: prefix = config.staff_payroll_sequence.prefix # ids = Transaction().context['active_ids'] # moves = [] # for payroll in Payroll.browse(ids): # if not payroll.move: # continue # moves.append(payroll.move) # print(payroll.number) report_context['prefix'] = prefix return report_context class PayrollExportStart(ModelView): 'Export Payroll Start' __name__ = 'staff.payroll.export.start' company = fields.Many2One('company.company', 'Company', required=True) start_period = fields.Many2One('staff.payroll.period', 'Start Period', required=True) end_period = fields.Many2One('staff.payroll.period', 'End Period', required=True) department = fields.Many2One('company.department', 'Department', required=False, depends=['employee']) @staticmethod def default_company(): return Transaction().context.get('company') class PayrollExport(Wizard): 'Payroll Export' __name__ = 'staff.payroll.export' start = StateView('staff.payroll.export.start', 'staff_payroll_co.payroll_export_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Print', 'print_', 'tryton-ok', default=True), ]) print_ = StateReport('staff.payroll.export_report') def do_print_(self, action): department_id = self.start.department.id \ if self.start.department else None data = { 'company': self.start.company.id, 'start_period': self.start.start_period.id, 'end_period': self.start.end_period.id, 'department_id': department_id, } return action, data def transition_print_(self): return 'end' class PayrollExportReport(Report): __name__ = 'staff.payroll.export_report' @classmethod def get_domain_payroll(cls, data=None): dom_payroll = [] return dom_payroll @classmethod def get_context(cls, records, data): report_context = super(PayrollExportReport, cls).get_context(records, data) pool = Pool() company = pool.get('company.company')(data['company']) Payroll = pool.get('staff.payroll') Period = pool.get('staff.payroll.period') dom_payroll = cls.get_domain_payroll() start_period, = Period.search([('id', '=', data['start_period'])]) end_period, = Period.search([('id', '=', data['end_period'])]) dom_payroll.append([ ('period.start', '>=', start_period.start), ('period.end', '<=', end_period.end), ('move', '!=', None) ]) if data['department_id'] not in (None, ''): dom_payroll.append([ ('employee.department', '=', data['department_id']) ]) payrolls = Payroll.search(dom_payroll, order=[('period.name', 'ASC')]) records = {} for payroll in payrolls: employee = payroll.employee """ extract debit account and party mandatory_wages""" accdb_party = dict((mw.wage_type.debit_account.id, mw.party) for mw in employee.mandatory_wages if mw.wage_type.debit_account) move = payroll.move for line in move.lines: """Check account code in dict account debit and party""" if line.account.id in accdb_party.keys(): line.party = accdb_party[line.account.id] if line.party: # id_number = line.party.id_number.split('-') id_number = line.party.id_number if id_number in ENTITY_ACCOUNTS.keys(): if line.debit: line.account.code = ENTITY_ACCOUNTS[id_number][1] elif line.credit: line.account.code = ENTITY_ACCOUNTS[id_number][0] if line.account.code not in records.keys(): records[line.account.code] = { 'name': line.account.name, 'lines': [] } records[line.account.code]['lines'].append(line) report_context['records'] = records report_context['start_date'] = start_period.name report_context['end_date'] = end_period.name report_context['company'] = company.party.name return report_context