odex25_standard/odex25_benefit/odex_benefit/models/family_expense.py

406 lines
20 KiB
Python

# -*- coding: utf-8 -*-
from dateutil.relativedelta import relativedelta
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from odoo.tools import date_utils
class ConfirmBenefitExpense(models.Model):
_name = 'confirm.benefit.expense'
_description = 'Confirm Benefit Expense'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = "family_expense_seq desc"
# region [Default Methods]
def _default_start_date(self):
today = fields.Date.today()
start_date = date_utils.start_of(today, 'month')
return start_date
def _default_end_date(self):
today = fields.Date.today()
end_date = date_utils.end_of(today, 'month')
return end_date
# endregion [Default Methods]
family_expense_seq = fields.Char(string="Number", copy=False, readonly=True,
default=lambda x: _('New'))
state = fields.Selection(selection=[
('draft', 'Draft'),
('calculated', 'Calculated'),
('assistant_general_manager', 'Waiting For The Assistant General Manager'),
('accounting_approve', 'Accounting Approve'),
('cancel', 'Cancelled'),
('confirm', 'Confirmed'),
], string='Status', default='draft', required=True, copy=False, tracking=True)
name = fields.Char(string="Name", states={'confirm': [('readonly', True)]}, copy=False)
date = fields.Date(string="Date", default=fields.Date.context_today, required=False,
states={'confirm': [('readonly', True)]})
start_date = fields.Date(string="Start Date", default=_default_start_date, required=True)
end_date = fields.Date(string="End Date", required=True, default=_default_end_date)
family_ids = fields.Many2many(comodel_name='grant.benefit', relation='benefit_expense_grant_rel',
column1='expense_id',
column2='family_id', string='Families', states={'confirm': [('readonly', True)]},
copy=False)
benefit_expense_line_ids = fields.One2many(comodel_name='benefit.expense.line', inverse_name='confirm_expense_id',
string='Benefit Expense Lines')
othaim_line_ids = fields.One2many(comodel_name='benefit.expense.line', inverse_name='confirm_expense_id',
string='Othaim Lines', domain=[('meal_card', '=', True)])
cash_expense = fields.Boolean(string='Include Cash Expense', default=True, states={'confirm': [('readonly', True)]})
meal_expense = fields.Boolean(string='Include Meal Expense', default=True, states={'confirm': [('readonly', True)]})
cloth_expense = fields.Boolean(string='Include Clothing Expense', default=True,
states={'confirm': [('readonly', True)]})
payment_order_id = fields.Many2one('payment.orders', string='Payment Order', ondelete="set null", copy=False)
move_id = fields.Many2one('account.move', ondelete='cascade')
available_payment_method_line_ids = fields.Many2many(comodel_name='account.payment.method.line')
family_monthly_income = fields.Float(string="Total Monthly Income", compute='_get_family_monthly_values',
store=True)
family_monthly_meals = fields.Float(string="Total Monthly Meals", compute='_get_family_monthly_values', store=True)
family_monthly_clotting = fields.Float(string="Total Monthly Clotting", compute='_get_family_monthly_values',
store=True)
family_monthly_othaime = fields.Float(string="Total Othaim", compute='_get_family_monthly_values', store=True)
family_monthly_total = fields.Float(string="Total", compute='_get_family_monthly_values', store=True)
branch_custom_ids = fields.Many2many(comodel_name='branch.settings', relation='confirm_benefit_expense_branch_rel',
column1='expense_id', column2='branch_id', string="Branches",
domain="[('has_employees', '=', True)]")
family_domain_ids = fields.Many2many(comodel_name='grant.benefit', compute='_compute_domain_ids')
company_id = fields.Many2one('res.company', default=lambda self: self.env.company)
currency_id = fields.Many2one(comodel_name='res.currency', string="Company Currency",
related='company_id.currency_id')
payment_state = fields.Selection(string='Payment State', selection=[
('none', 'None'),
('waiting', 'Waiting Payment'),
('done', 'Done Payment'), ], copy=False, compute="_compute_payment_move_state", store=True)
move_state = fields.Selection(string='Move State', selection=[
('none', 'None'),
('waiting', 'Waiting Payment'),
('done', 'Done Payment'), ], copy=False, compute="_compute_payment_move_state", store=True)
family_count_expense = fields.Integer(string="Family Count (Monthly Expense)",
compute='_get_family_monthly_values', store=True)
family_count_othaim = fields.Integer(string="Family Count (Othaim)", compute='_get_family_monthly_values',
store=True)
member_count_expense = fields.Integer(string="Member Count (Monthly Expense)", compute='_get_family_monthly_values',
store=True, )
member_count_othaim = fields.Integer(string="Member Count (Othaim)", compute='_get_family_monthly_values',
store=True, )
is_return_calculation = fields.Boolean(string="Return Calculation Mode", default=False,
help="Enable to calculate returned amounts instead of regular monthly expense."
)
return_expense_line_ids = fields.One2many(comodel_name='benefit.expense.line', inverse_name='return_confirm_id',
string="Selected Return Lines",
domain="[('is_return', '=', True)]", )
line_domain_ids = fields.Many2many(comodel_name='benefit.expense.line', compute='_compute_domain_ids',
string="Return Line Domain",
)
@api.depends('payment_order_id', 'payment_order_id.state', 'move_id', 'move_id.state')
def _compute_payment_move_state(self):
for rec in self:
payment_state = 'none'
move_state = 'none'
if rec.payment_order_id:
if rec.payment_order_id.state == "done":
payment_state = "done"
else:
payment_state = "waiting"
if rec.move_id:
if rec.move_id.state == "posted":
move_state = "done"
else:
move_state = "waiting"
rec.move_state = move_state
rec.payment_state = payment_state
if rec.move_state == 'done' and rec.payment_state == 'done':
rec.state = 'confirm'
@api.model
def create(self, vals):
res = super(ConfirmBenefitExpense, self).create(vals)
if not res.family_ids:
raise UserError(_('Select Family'))
if not res.family_expense_seq or res.family_expense_seq == _('New'):
res.family_expense_seq = self.env['ir.sequence'].sudo().next_by_code('family.expense.sequence') or _('New')
return res
def _update_benefit_expense_lines(self):
self.ensure_one()
for line in self.benefit_expense_line_ids:
family = line.family_id
income, meals, clotting, othaim = 0, 0, 0, 0
if not family:
continue
monthly_meals = 0.0 if family.meal_card else family.family_monthly_meals
othaime = family.family_monthly_meals if family.meal_card else 0.0
if self.cash_expense:
income = family.family_monthly_income
if self.meal_expense:
meals = monthly_meals
othaim = othaime
if self.cloth_expense:
clotting = family.family_monthly_clotting
vals = {
'branch_id': family.branch_custom_id.id,
'family_category_id': family.benefit_category_id.id,
'meal_card': family.meal_card,
'benefit_member_count': family.benefit_member_count,
'start_date': self.start_date,
'end_date': self.end_date,
'family_monthly_income': income,
'family_monthly_meals': meals,
'family_monthly_clotting': clotting,
'family_monthly_othaime': othaim,
}
line.write(vals)
def _calculate_return_lines(self):
self.ensure_one()
return_lines = self.return_expense_line_ids
if not return_lines:
raise UserError(_("Please select at least one return line to calculate."))
lines = []
for line in return_lines:
lines.append((0, 0, {
'family_id': line.family_id.id,
'branch_id': line.branch_id.id,
'family_category_id': line.family_category_id.id,
'meal_card': line.meal_card,
'benefit_member_count': line.benefit_member_count,
'start_date': self.start_date,
'end_date': self.end_date,
'family_monthly_income': line.family_monthly_income,
'family_monthly_meals': line.family_monthly_meals,
'family_monthly_clotting': line.family_monthly_clotting,
'family_monthly_othaime': 0,
}))
self.benefit_expense_line_ids = lines
def action_calculate(self):
for rec in self:
if rec.state != 'draft':
raise UserError(_("You can only calculate in draft state."))
families = rec.family_ids
if not families:
raise UserError(_("Please select at least one family to calculate."))
if not rec.cash_expense and not rec.meal_expense and not rec.cloth_expense:
raise UserError(_("At least one expense type should be selected."))
if rec.is_return_calculation:
rec._calculate_return_lines()
else:
rec.benefit_expense_line_ids.unlink()
lines = []
for fam in families:
vals = {
'confirm_expense_id': rec.id,
'family_id': fam.id,
'start_date': rec.start_date,
'end_date': rec.end_date,
}
lines.append((0, 0, vals))
rec.write({'benefit_expense_line_ids': lines})
rec._update_benefit_expense_lines()
rec.state = 'calculated'
def action_recalculate(self):
for rec in self:
if rec.state != 'calculated':
raise UserError(_("You can only recalculate when status is 'Calculated'."))
rec._update_benefit_expense_lines()
@api.depends('is_return_calculation', 'branch_custom_ids', 'start_date', 'end_date')
def _compute_domain_ids(self):
for rec in self:
Line = self.env['benefit.expense.line']
validation_setting = self.env["family.validation.setting"].search([], limit=1)
if rec.is_return_calculation:
domain = [
('start_date', '<=', rec.end_date),
('end_date', '>=', rec.start_date),
('is_return', '=', True),
('return_reason_id', '!=', False),
('return_confirm_id', '=', False),
]
if rec.branch_custom_ids:
domain.append(('branch_id', 'in', rec.branch_custom_ids.ids))
return_lines = Line.search(domain)
rec.line_domain_ids = return_lines
rec.family_domain_ids = return_lines.mapped('family_id')
else:
# Define base domain for family selection
base_domain = ['|', ('state', '=', 'second_approve'), '&',
('state', 'in', ('waiting_approve', 'first_approve')), ('action_type', '=', 'suspended')]
if rec.branch_custom_ids:
base_domain.append(('branch_custom_id', 'in', rec.branch_custom_ids.ids))
min_income = validation_setting.benefit_category_ids.mapped('mini_income_amount')
max_income = validation_setting.benefit_category_ids.mapped('max_income_amount')
base_domain.extend([('member_income', '>=', min(min_income)), ('member_income', '<=', max(max_income))])
base_domain.extend([('benefit_category_id', '!=', False)])
if rec.start_date and rec.end_date:
conflicting_records = self.search([
('id', '!=', rec._origin.id),
('start_date', '<=', rec.end_date),
('end_date', '>=', rec.start_date),
])
if conflicting_records:
conflicting_family_ids = conflicting_records.mapped('family_ids').ids
base_domain.append(('id', 'not in', conflicting_family_ids))
rec.family_domain_ids = self.env['grant.benefit'].search(base_domain)
rec.line_domain_ids = self.env['benefit.expense.line'].browse([])
@api.onchange('branch_custom_ids')
def _onchange_branch_custom_ids(self):
if self.branch_custom_ids:
allowed_families = self.env['grant.benefit'].search([
('id', 'in', self.family_ids.ids),
('branch_custom_id', 'in', self.branch_custom_ids.ids),
])
self.family_ids = [(6, 0, allowed_families.ids)]
else:
self.family_ids = [(5, 0, 0)]
def unlink(self):
for rec in self:
if rec.state not in ['draft']:
raise UserError(_('This record can only be deleted in draft state.'))
return super(ConfirmBenefitExpense, self).unlink()
@api.depends('benefit_expense_line_ids')
def _get_family_monthly_values(self):
for rec in self:
lines = rec.benefit_expense_line_ids
rec.family_monthly_income = sum(lines.mapped('family_monthly_income'))
rec.family_monthly_meals = sum(lines.mapped('family_monthly_meals'))
rec.family_monthly_clotting = sum(lines.mapped('family_monthly_clotting'))
rec.family_monthly_othaime = sum(lines.mapped('family_monthly_othaime'))
rec.family_monthly_total = rec.family_monthly_income + rec.family_monthly_meals + rec.family_monthly_clotting
expense_lines = lines.filtered(lambda l: not l.meal_card)
othaim_lines = lines.filtered('meal_card')
rec.family_count_expense = len(expense_lines.mapped('family_id'))
rec.member_count_expense = sum(expense_lines.mapped('benefit_member_count'))
rec.family_count_othaim = len(othaim_lines.mapped('family_id'))
rec.member_count_othaim = sum(othaim_lines.mapped('benefit_member_count'))
def action_assistant_manager(self):
for family in self.family_ids:
if self.end_date and family.last_disbursement_date:
if self.end_date > family.last_disbursement_date:
family.last_disbursement_date = self.end_date
else:
family.last_disbursement_date = self.end_date
self.state = 'assistant_general_manager'
def action_accounting_approve(self):
self.state = 'accounting_approve'
def action_cancel(self):
self.state = 'cancel'
def action_reset_to_draft(self):
self.payment_order_id.unlink()
self.move_id.unlink()
self.benefit_expense_line_ids.unlink()
self.state = 'draft'
def action_open_related_move_records(self):
moves = self.move_id.ids
return {
'name': _('Vendor Bills'),
'type': 'ir.actions.act_window',
'res_model': 'account.move',
'view_mode': 'tree,form',
'domain': [('id', 'in', moves)],
}
def action_open_related_payment_orders(self):
payment_orders = self.payment_order_id.ids
return {
'name': _('Payment Orders'),
'type': 'ir.actions.act_window',
'res_model': 'payment.orders',
'view_mode': 'tree,form',
'domain': [('id', 'in', payment_orders)],
}
def action_accounting_transfer(self):
for rec in self:
validation_setting = self.env["family.validation.setting"].search([], limit=1)
lines = rec.benefit_expense_line_ids
if not lines:
raise UserError(_("Please make sure you have benefit expense lines."))
families = lines.mapped('family_id')
invalid_families = families.filtered(
lambda f: f.state != 'second_approve'
or (f.state in ('waiting_approve', 'first_approve') and f.action_type == 'suspended')
)
if invalid_families:
raise UserError(_(
"Some selected benefits are not in valid state or are suspended:\n%s"
) % ", ".join(invalid_families.mapped('name')))
if not validation_setting.cash_expense_account_id or not validation_setting.meal_expense_account_id or not validation_setting.clothing_expense_account_id:
raise UserError(_("Please configure the expense accounts in the validation settings."))
credit_account_id = validation_setting.account_id.id
if not credit_account_id:
raise UserError(_("Please select credit account."))
# todo if have paymnet or move dont create again
# Create Payment Order for Benefit Expense
payment_order = self.env['payment.orders'].create({
'state': 'draft',
'accountant_id': validation_setting.accountant_id.id,
'benefit_expense_line_ids': [(6, 0, rec.benefit_expense_line_ids.ids)],
'type': 'benefit_expense',
})
rec.payment_order_id = payment_order
if not rec.is_return_calculation:
# Create Vendor Bill for Meal Card Invoice(othaime)
account_id = validation_setting.meal_expense_account_id
invoice_lines = []
for line in lines.filtered(lambda l: l.meal_card):
family = line.family_id
invoice_lines.append((0, 0, {
'name': f'{family.name}/{family.code}',
'account_id': account_id.id,
'quantity': 1,
'benefit_family_id': family.id,
'price_unit': line.family_monthly_othaime,
'family_confirm_id': rec.id,
'analytic_account_id': family.branch_family_id.branch.analytic_account_id.id
}))
invoice_vals = {
'move_type': 'in_invoice',
'partner_id': validation_setting.meal_partner_id.id,
'invoice_date': rec.date,
'family_confirm_id': rec.id,
'benefit_family_ids': [(6, 0, rec.benefit_expense_line_ids.mapped('family_id').ids)],
'journal_id': validation_setting.journal_id.id,
'invoice_line_ids': invoice_lines,
'ref': rec.name,
}
invoice = self.env['account.move'].create(invoice_vals)
rec.move_id = invoice
return True