from odoo import api, fields, models from odoo.exceptions import ValidationError class McFeeSchedule(models.Model): _name = "mc.fee.schedule" _description = "Fee Installment Schedule" _order = "structure_id" _rec_name = "display_name" structure_id = fields.Many2one( "mc.fee.structure", string="Fee Structure", required=True, ondelete="cascade", ) line_ids = fields.One2many("mc.fee.schedule.line", "schedule_id", string="Installments") total_percentage = fields.Float( string="Total %", compute="_compute_total_percentage", store=True, help="Must reach exactly 100% before this schedule can be used to generate an " "invoice - checked at that point, not while you are still building it up " "term by term.", ) _structure_uniq = models.Constraint( "unique(structure_id)", "This fee structure already has an installment schedule.", ) @api.depends("structure_id.display_name") def _compute_display_name(self): for schedule in self: schedule.display_name = "%s installments" % (schedule.structure_id.display_name or "?") @api.depends("line_ids.percentage") def _compute_total_percentage(self): for schedule in self: schedule.total_percentage = sum(schedule.line_ids.mapped("percentage")) class McFeeScheduleLine(models.Model): _name = "mc.fee.schedule.line" _description = "Fee Installment" _order = "due_date" schedule_id = fields.Many2one( "mc.fee.schedule", string="Schedule", required=True, ondelete="cascade", ) term_id = fields.Many2one("mc.academic.term", string="Term", required=True, ondelete="restrict") due_date = fields.Date(string="Due Date", required=True) percentage = fields.Float(string="Percentage", required=True) _percentage_range = models.Constraint( "check(percentage > 0 and percentage <= 100)", "An installment percentage must be between 0 and 100.", ) _schedule_term_uniq = models.Constraint( "unique(schedule_id, term_id)", "This schedule already has an installment for this term.", ) @api.constrains("percentage", "schedule_id") def _check_schedule_not_over_100(self): # Only guards against clearly-wrong data (allocating more than the # whole fee) at write time. Reaching exactly 100% is expected to # take several saves as terms are added one at a time - and is # enforced instead at the point it actually matters: when the # invoice-generation wizard resolves a schedule to use (see # wizards/mc_fee_invoice_generate_wizard.py). for line in self: total = sum(line.schedule_id.line_ids.mapped("percentage")) if total > 100.01: raise ValidationError( "The installments for '%s' add up to more than 100%% (%.2f%%)." % ( line.schedule_id.display_name, total, ) )