from odoo import api, fields, models, _ from odoo.exceptions import ValidationError from datetime import timedelta class EducationLibraryIssue(models.Model): _name = 'education.library.issue' _description = 'Library Book Issue' _rec_name = 'name' _order = 'issue_date desc' name = fields.Char(string='Reference', required=True, copy=False, readonly=True, default=lambda self: _('New')) member_id = fields.Many2one('education.library.member', string='Member', required=True) copy_id = fields.Many2one('education.library.book.copy', string='Book Copy', required=True) library_id = fields.Many2one('education.library', string='Library', related='copy_id.library_id', store=True) issue_date = fields.Date(string='Issue Date', default=fields.Date.context_today, required=True) due_date = fields.Date(string='Due Date', compute='_compute_due_date', store=True, readonly=False) return_date = fields.Date(string='Return Date') status = fields.Selection([ ('draft', 'Draft'), ('issued', 'Issued'), ('returned', 'Returned'), ('overdue', 'Overdue'), ('cancelled', 'Cancelled') ], string='Status', default='draft', required=True) remarks = fields.Text(string='Remarks') fine_amount = fields.Float(string='Fine Amount', compute='_compute_fine_amount') fine_ids = fields.One2many('education.library.fine', 'issue_id', string='Fines') @api.model_create_multi def create(self, vals_list): for vals in vals_list: if vals.get('name', _('New')) == _('New'): vals['name'] = self.env['ir.sequence'].sudo().next_by_code('education.library.issue') or _('New') return super().create(vals_list) @api.depends('issue_date') def _compute_due_date(self): loan_days = int(self.env['ir.config_parameter'].sudo().get_param('education_library.loan_period_days', 14)) for record in self: if record.issue_date and not record.due_date: record.due_date = record.issue_date + timedelta(days=loan_days) @api.depends('fine_ids', 'fine_ids.amount') def _compute_fine_amount(self): for record in self: record.fine_amount = sum(record.fine_ids.mapped('amount')) @api.constrains('issue_date', 'due_date') def _check_dates(self): for record in self: if record.due_date < record.issue_date: raise ValidationError(_('Due date must be greater than or equal to issue date.')) def action_issue(self): for record in self: if record.copy_id.status != 'available': raise ValidationError(_('The selected book copy is not available for issue.')) if not record.member_id.active: raise ValidationError(_('Active member required.')) if record.member_id.membership_end and record.member_id.membership_end < fields.Date.today(): raise ValidationError(_('Membership dates do not permit issue.')) # Check issue limit active_issues = self.search_count([ ('member_id', '=', record.member_id.id), ('status', 'in', ['issued', 'overdue']) ]) if active_issues >= record.member_id.issue_limit: raise ValidationError(_('Issue limit enforced: Member has reached their maximum active issues limit.')) record.copy_id.status = 'issued' record.status = 'issued' def action_return(self): for record in self: if record.status not in ['issued', 'overdue']: raise ValidationError(_('Only issued or overdue records can be returned.')) return_date = fields.Date.today() record.return_date = return_date # Calculate fine if late days_late = max((return_date - record.due_date).days, 0) if days_late > 0: fine_per_day = float(self.env['ir.config_parameter'].sudo().get_param('education_library.fine_per_day', 10.0)) fine_amt = days_late * fine_per_day if fine_amt > 0: self.env['education.library.fine'].create({ 'issue_id': record.id, 'member_id': record.member_id.id, 'student_id': record.member_id.student_id.id if record.member_id.student_id else False, 'days_late': days_late, 'amount': fine_amt, 'state': 'draft' }) record.copy_id.status = 'available' record.status = 'returned' def action_cancel(self): for record in self: if record.status not in ['draft']: raise ValidationError(_('Only draft issues can be cancelled.')) record.status = 'cancelled' @api.model def _cron_mark_overdue(self): overdue_issues = self.search([ ('status', '=', 'issued'), ('due_date', '<', fields.Date.today()) ]) overdue_issues.write({'status': 'overdue'})