forked from Mapan/odoo17e
228 lines
8.5 KiB
Python
228 lines
8.5 KiB
Python
# -*- coding:utf-8 -*-
|
||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
|
||
import base64
|
||
import csv
|
||
import io
|
||
|
||
from collections import defaultdict
|
||
from datetime import date
|
||
from dateutil.relativedelta import relativedelta
|
||
|
||
from odoo import api, fields, models, _
|
||
from odoo.exceptions import UserError
|
||
|
||
|
||
class L10nUsW2(models.Model):
|
||
_name = 'l10n.us.w2'
|
||
_description = 'W2 Form'
|
||
|
||
@api.model
|
||
def default_get(self, field_list=None):
|
||
if self.env.company.country_id.code != "US":
|
||
raise UserError(_('You must be logged in a US company to use this feature'))
|
||
return super().default_get(field_list)
|
||
|
||
date_start = fields.Date("Start Date", default=lambda s: fields.Date.today() + relativedelta(day=1, month=1))
|
||
date_end = fields.Date("End Date", compute='_compute_date_end', store=True, readonly=False)
|
||
company_id = fields.Many2one(
|
||
'res.company',
|
||
default=lambda self: self.env.company,
|
||
domain=lambda self: [('id', 'in', self.env.companies.ids)],
|
||
required=True)
|
||
allowed_payslip_ids = fields.Many2many('hr.payslip', compute='_compute_allowed_payslip_ids')
|
||
payslip_ids = fields.Many2many(
|
||
'hr.payslip',
|
||
compute='_compute_payslips_ids',
|
||
store=True,
|
||
readonly=False,
|
||
domain="[('id', 'in', allowed_payslip_ids)]")
|
||
csv_file = fields.Binary("CSV file", readonly=True)
|
||
csv_filename = fields.Char()
|
||
|
||
@api.depends('date_start')
|
||
def _compute_date_end(self):
|
||
for w2 in self:
|
||
if not w2.date_start:
|
||
continue
|
||
w2.date_end = date(w2.date_start.year, 12, 31)
|
||
|
||
@api.depends('date_end')
|
||
def _compute_display_name(self):
|
||
for w2 in self:
|
||
w2.display_name = f"W-2 Form - {w2.date_end.year}" if w2.date_end else "W-2 Form"
|
||
|
||
@api.depends('allowed_payslip_ids')
|
||
def _compute_payslips_ids(self):
|
||
for w2 in self:
|
||
w2.payslip_ids = w2.allowed_payslip_ids
|
||
|
||
def _get_allowed_payslips_domain(self):
|
||
self.ensure_one()
|
||
return [
|
||
('state', 'in', ['done', 'paid']),
|
||
('date_from', '<=', self.date_end),
|
||
('date_to', '>=', self.date_start),
|
||
('company_id', '=', self.company_id.id),
|
||
]
|
||
|
||
@api.depends('company_id', 'date_start', 'date_end')
|
||
def _compute_allowed_payslip_ids(self):
|
||
for w2 in self:
|
||
if not w2.date_start or not w2.date_end or not w2.company_id:
|
||
w2.allowed_payslip_ids = [(5, 0, 0)]
|
||
else:
|
||
allowed_payslips = self.env['hr.payslip'].search(w2._get_allowed_payslips_domain())
|
||
w2.allowed_payslip_ids = allowed_payslips
|
||
|
||
def action_generate_csv(self):
|
||
self.ensure_one()
|
||
header = [
|
||
"Employer identification number (EIN)",
|
||
"Employer's name",
|
||
"Street address 1",
|
||
"Street address 2",
|
||
"City or town",
|
||
"State or province",
|
||
"Country",
|
||
"ZIP code",
|
||
"Telephone no.",
|
||
"Email",
|
||
"Employee’s social security number (SSN)",
|
||
"Employee's first name",
|
||
"Employee's middle initial",
|
||
"Employee's last name",
|
||
"Street address 1",
|
||
"Street address 2",
|
||
"City or town",
|
||
"State or province",
|
||
"Country",
|
||
"ZIP code",
|
||
"Telephone no.",
|
||
"Email",
|
||
"Control number",
|
||
"1 Wages, tips, other compensation",
|
||
"2 Federal income tax withheld",
|
||
"3 Social security wages",
|
||
"4 Social security tax withheld",
|
||
"5 Medicare wages and tips",
|
||
"6 Medicare tax withheld",
|
||
"7 Social security tips",
|
||
"8 Allocated tips",
|
||
"10 Dependent care benefits",
|
||
"11 Nonqualified plans",
|
||
"12a Code A",
|
||
"12a Amount A",
|
||
"12b Code B",
|
||
"12b Amount B",
|
||
"12c Code C",
|
||
"12c Amount C",
|
||
"12d Code D",
|
||
"12d Amount D",
|
||
"13 Statutory employee",
|
||
"13 Retirement plan",
|
||
"13 Third-party sick pay",
|
||
"14 Other A",
|
||
"14 Other amount A",
|
||
"14 Other B",
|
||
"14 Other amount B",
|
||
"14 Other C",
|
||
"14 Other amount C",
|
||
"File directly with state",
|
||
"15 State",
|
||
"15 Employer’s state ID number",
|
||
"16 State wages, tips, etc.",
|
||
"17 State income tax",
|
||
"18 Local wages, tips, etc.",
|
||
"19 Local income tax",
|
||
"20 Locality name",
|
||
"File directly with state",
|
||
"Kind of payer",
|
||
"Kind of employer",
|
||
"Terminating Business",
|
||
]
|
||
|
||
output = io.StringIO()
|
||
writer = csv.writer(output)
|
||
writer.writerow(header)
|
||
|
||
payslips_by_employee = defaultdict(lambda: self.env['hr.payslip'])
|
||
for payslip in self.payslip_ids:
|
||
payslips_by_employee[payslip.employee_id] += payslip
|
||
|
||
for employee, payslips in payslips_by_employee.items():
|
||
line_values = payslips._get_line_values([
|
||
'TAXABLE', 'FIT', '401K', 'TIPS', 'SST', 'MEDICARE', 'MEDICAREADD', 'ALLOCATEDTIPS',
|
||
'MEDICALFSADC', 'MEDICALHSA', 'ROTH401K',
|
||
'CAINCOMETAX', 'NYINCOMETAX',
|
||
'CASDITAX', 'NYSDITAX',
|
||
], compute_sum=True)
|
||
|
||
writer.writerow([
|
||
self.company_id.vat or "",
|
||
self.company_id.name or "",
|
||
self.company_id.street or "",
|
||
self.company_id.street2 or "",
|
||
self.company_id.city or "",
|
||
self.company_id.state_id.code or "",
|
||
self.company_id.country_id.name or "",
|
||
self.company_id.zip or "",
|
||
self.company_id.phone or "",
|
||
self.company_id.email or "",
|
||
employee.ssnid or "",
|
||
employee.name or "",
|
||
"",
|
||
employee.name or "",
|
||
employee.private_street or "",
|
||
employee.private_street2 or "",
|
||
employee.private_city or "",
|
||
employee.private_state_id.code or "",
|
||
employee.private_country_id.name or "",
|
||
employee.private_zip or "",
|
||
employee.private_phone or "",
|
||
employee.private_email or "",
|
||
"",
|
||
abs(line_values['TAXABLE']['sum']['total']),
|
||
abs(line_values['FIT']['sum']['total']),
|
||
abs(line_values['TAXABLE']['sum']['total'] - line_values['401K']['sum']['total'] - line_values['TIPS']['sum']['total']),
|
||
abs(line_values['SST']['sum']['total']),
|
||
abs(line_values['TAXABLE']['sum']['total'] - line_values['401K']['sum']['total']),
|
||
abs(line_values['MEDICARE']['sum']['total'] + line_values['MEDICAREADD']['sum']['total']),
|
||
abs(line_values['TIPS']['sum']['total']),
|
||
abs(line_values['ALLOCATEDTIPS']['sum']['total']),
|
||
abs(line_values['MEDICALFSADC']['sum']['total']),
|
||
"",
|
||
"D",
|
||
abs(line_values['401K']['sum']['total']),
|
||
"W",
|
||
abs(line_values['MEDICALHSA']['sum']['total']),
|
||
"AA",
|
||
abs(line_values['ROTH401K']['sum']['total']),
|
||
"",
|
||
"",
|
||
"TRUE" if employee.l10n_us_statutory_employee else "FALSE",
|
||
"TRUE" if employee.l10n_us_retirement_plan else "FALSE",
|
||
"TRUE" if employee.l10n_us_third_party_sick_pay else "FALSE",
|
||
_("%s SDI Tax", employee.address_id.state_id.code) if employee.address_id.state_id.code in ['NY', 'CA'] else "",
|
||
abs(line_values['CASDITAX']['sum']['total'] + line_values['NYSDITAX']['sum']['total']),
|
||
"",
|
||
"",
|
||
"",
|
||
"",
|
||
"",
|
||
employee.address_id.state_id.code or "",
|
||
self.company_id.company_registry or "",
|
||
abs(line_values['TAXABLE']['sum']['total']),
|
||
abs(line_values['CAINCOMETAX']['sum']['total'] + line_values['NYINCOMETAX']['sum']['total']),
|
||
"",
|
||
"",
|
||
"",
|
||
"",
|
||
"R",
|
||
"N",
|
||
"",
|
||
])
|
||
|
||
self.csv_file = base64.b64encode(output.getvalue().encode())
|
||
self.csv_filename = f"form_w2_{self.date_end.year}.csv"
|