1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/l10n_au_keypay/models/res_company.py
2024-12-10 09:04:09 +07:00

139 lines
7.3 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import requests
from datetime import datetime
from collections import defaultdict
from werkzeug.urls import url_join
from odoo import _, api, fields, models
from odoo.exceptions import UserError, AccessError
from odoo.tools.misc import format_date, format_datetime
class res_company(models.Model):
_inherit = 'res.company'
l10n_au_kp_enable = fields.Boolean(string='Enable Employment Hero Integration')
l10n_au_kp_identifier = fields.Char(string='Business Id')
l10n_au_kp_lock_date = fields.Date(string='Fetch Payrun After', help="Import payruns paied after this date. This date cannot be prior to Lock Date)")
l10n_au_kp_journal_id = fields.Many2one('account.journal', string='Payroll Journal')
@api.onchange('fiscalyear_lock_date', 'l10n_au_kp_lock_date')
def _onchange_exclude_before(self):
self.l10n_au_kp_lock_date = max(self.l10n_au_kp_lock_date, self.fiscalyear_lock_date)
def _kp_get_key_and_url(self):
key = self.env['ir.config_parameter'].get_param('l10n_au_keypay.l10n_au_kp_api_key')
l10n_au_kp_base_url = self.env['ir.config_parameter'].get_param('l10n_au_keypay.l10n_au_kp_base_url')
return (key, l10n_au_kp_base_url)
def _kp_payroll_fetch_journal_entries(self, kp_payrun):
self.ensure_one()
key, l10n_au_kp_base_url = self._kp_get_key_and_url()
# Fetch the journal details: https://api.keypay.com.au/australia/reference/pay-run/au-journal--get
url = url_join(l10n_au_kp_base_url, 'api/v2/business/%s/journal/%s' % (self.l10n_au_kp_identifier, kp_payrun['id']))
response = requests.get(url, auth=(key, ''), timeout=10)
response.raise_for_status()
line_ids_commands = []
tax_results = defaultdict(lambda: {'debit': 0, 'credit': 0})
for kp_journal_item in response.json():
item_account = self.env['account.account'].search([
*self.env['account.account']._check_company_domain(self),
('deprecated', '=', False),
'|', ('l10n_au_kp_account_identifier', '=', kp_journal_item['accountCode']), ('code', '=', kp_journal_item['accountCode'])
], limit=1, order='l10n_au_kp_account_identifier')
if not item_account:
raise UserError(_("Account not found: %s, either create an account with that code or link an existing one to that Employment Hero code", kp_journal_item['accountCode']))
tax = False
if kp_journal_item.get('taxCode'):
tax = self.env['account.tax'].search([
*self.env['account.tax']._check_company_domain(self),
('l10n_au_kp_tax_identifier', '=', kp_journal_item['taxCode'])], limit=1)
if tax:
tax_compute_result = self.currency_id.round(tax.with_context(force_price_include=True)._compute_amount(abs(kp_journal_item['amount']), 1.0))
tax_results[tax.id]['debit' if kp_journal_item['isDebit'] else 'credit'] += tax_compute_result
amount = abs(kp_journal_item['amount']) - tax_compute_result
else:
amount = abs(kp_journal_item['amount'])
line_ids_commands.append((0, 0, {
'account_id': item_account.id,
'name': kp_journal_item['reference'],
'debit': amount if kp_journal_item['isDebit'] else 0,
'credit': amount if kp_journal_item['isCredit'] else 0,
'tax_ids': [(4, tax.id, 0)] if tax else False,
}))
period_ending_date = datetime.strptime(kp_payrun["payPeriodEnding"], "%Y-%m-%dT%H:%M:%S")
move = self.env['account.move'].create({
'journal_id': self.l10n_au_kp_journal_id.id,
'ref': _("Pay period ending %s (#%s)", format_date(self.env, period_ending_date), kp_payrun['id']),
'date': datetime.strptime(kp_payrun["datePaid"], "%Y-%m-%dT%H:%M:%S"),
'line_ids': line_ids_commands,
'l10n_au_kp_payrun_identifier': kp_payrun['id'],
})
move_update_vals = []
for move_line in move.line_ids.filtered(lambda l: l.tax_line_id):
line_val = {}
if move_line.debit:
line_val['debit'] = tax_results[move_line.tax_line_id.id]['debit']
else:
line_val['credit'] = tax_results[move_line.tax_line_id.id]['credit']
move_update_vals.append((1, move_line.id, line_val))
move.write({'line_ids': move_update_vals})
return move
def _kp_payroll_fetch_payrun(self):
self.ensure_one()
if not self.env.user.has_group('account.group_account_manager'):
raise AccessError(_("You don't have the access rights to fetch Employment Hero payrun."))
key, l10n_au_kp_base_url = self._kp_get_key_and_url()
if not key or not self.l10n_au_kp_identifier or not self.l10n_au_kp_journal_id:
raise UserError(_("Company %s does not have the apikey, business_id or the journal_id set", self.name))
from_formatted_datetime = self.l10n_au_kp_lock_date and datetime.combine(self.l10n_au_kp_lock_date, datetime.min.time()).replace(hour=23, minute=59, second=59)
from_formatted_datetime = format_datetime(self.env, from_formatted_datetime, dt_format="yyyy-MM-dd'T'HH:mm:ss", tz='UTC')
keypay_filter = "$filter=DatePaid gt datetime'%s'&" % (from_formatted_datetime) if from_formatted_datetime else ''
skip = 0
top = 100
kp_payruns = []
while True:
# Fetch the pay runs: https://api.keypay.com.au/australia/reference/pay-run/au-pay-run--get-pay-runs
# Use Odata filtering (can only fetch 100 entries at a time): https://api.keypay.com.au/guides/ODataFiltering
# There is a limit of 5 requests per second but the api do not discard the requests it just waits every 5 answers: https://api.keypay.com.au/guides/Usage
url = url_join(l10n_au_kp_base_url, "api/v2/business/%s/payrun?%s$skip=%d&$top=%d" % (self.l10n_au_kp_identifier, keypay_filter, skip, top))
response = requests.get(url, auth=(key, ''), timeout=10)
response.raise_for_status()
entries = response.json()
kp_payruns += entries
if len(entries) < 100:
break
skip += 100
top += 100
# We cannot filter using the API as we might run into a 414 Client Error: Request-URI Too Large
payrun_ids = [kp_payrun['id'] for kp_payrun in kp_payruns]
processed_payrun_ids = self.env['account.move'].search([('company_id', '=', self.id), ('l10n_au_kp_payrun_identifier', 'in', payrun_ids)])
processed_payruns = processed_payrun_ids.mapped('l10n_au_kp_payrun_identifier')
account_moves = self.env['account.move']
for kp_payrun in kp_payruns:
# Entry needs to be finalized to have a journal entry
# Currently no way to filter on boolean via the API...
if not kp_payrun['isFinalised'] or kp_payrun['id'] in processed_payruns:
continue
move = self._kp_payroll_fetch_journal_entries(kp_payrun)
account_moves += move
return account_moves
def _kp_payroll_cron_fetch_payrun(self):
for company in self.search([('l10n_au_kp_enable', '=', True)]):
company._kp_payroll_fetch_payrun()