import logging from odoo import http from odoo.http import request from odoo.addons.encoach_api.controllers.base import ( jwt_required, _json_response, _error_response, _get_json_body, _paginate, ) _logger = logging.getLogger(__name__) class LibraryController(http.Controller): # ── Media ──────────────────────────────────────────────────────── @http.route('/api/library/media', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def list_media(self, **kw): try: M = request.env['op.media'].sudo() offset, limit, page = _paginate(kw) total = M.search_count([]) recs = M.search([], offset=offset, limit=limit, order='id desc') items = [{ 'id': r.id, 'name': r.name or '', 'isbn': getattr(r, 'isbn', '') or '', 'author': ', '.join(a.name for a in r.author_ids) if hasattr(r, 'author_ids') else '', 'edition': getattr(r, 'edition', '') or '', 'media_type': r.media_type_id.name if hasattr(r, 'media_type_id') and r.media_type_id else '', } for r in recs] return _json_response({'items': items, 'data': items, 'total': total, 'page': page, 'size': limit}) except Exception as e: _logger.exception('list_media') return _error_response(str(e), 500) @http.route('/api/library/media', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def create_media(self, **kw): try: body = _get_json_body() name = (body.get('name') or '').strip() if not name: return _error_response('Name is required', 400) Media = request.env['op.media'].sudo() vals = {'name': name} if body.get('isbn'): vals['isbn'] = body['isbn'] if body.get('edition'): vals['edition'] = body['edition'] author_names = [] raw_author = body.get('author') if isinstance(raw_author, str) and raw_author.strip(): author_names = [a.strip() for a in raw_author.split(',') if a.strip()] raw_author_ids = body.get('author_ids') author_ids = [] if isinstance(raw_author_ids, (list, tuple)): author_ids = [int(a) for a in raw_author_ids if a] if author_names and 'author_ids' in Media._fields: Author = request.env['op.author'].sudo() if 'op.author' in request.env else None if Author is not None: for nm in author_names: existing = Author.search([('name', '=', nm)], limit=1) if not existing: existing = Author.create({'name': nm}) author_ids.append(existing.id) if author_ids and 'author_ids' in Media._fields: vals['author_ids'] = [(6, 0, list(set(author_ids)))] rec = Media.create(vals) return _json_response({'data': {'id': rec.id, 'name': rec.name}}) except Exception as e: _logger.exception('create_media') return _error_response(str(e), 500) @http.route('/api/library/media/', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def get_media(self, mid, **kw): try: rec = request.env['op.media'].sudo().browse(mid) if not rec.exists(): return _error_response('Not found', 404) author_names = [a.name for a in rec.author_ids] if hasattr(rec, 'author_ids') else [] return _json_response({ 'id': rec.id, 'name': rec.name or '', 'isbn': getattr(rec, 'isbn', '') or '', 'edition': getattr(rec, 'edition', '') or '', 'author_ids': rec.author_ids.ids if hasattr(rec, 'author_ids') else [], 'author_names': author_names, 'author': ', '.join(author_names), 'publisher_ids': rec.publisher_ids.ids if hasattr(rec, 'publisher_ids') else [], 'media_type': rec.media_type_id.name if hasattr(rec, 'media_type_id') and rec.media_type_id else '', }) except Exception as e: return _error_response(str(e), 500) @http.route('/api/library/media/', type='http', auth='public', methods=['DELETE'], csrf=False) @jwt_required def delete_media(self, mid, **kw): try: rec = request.env['op.media'].sudo().browse(mid) if rec.exists(): rec.unlink() return _json_response({'success': True}) except Exception as e: return _error_response(str(e), 500) # ── Movements ──────────────────────────────────────────────────── @http.route('/api/library/movements', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def list_movements(self, **kw): try: M = request.env['op.media.movement'].sudo() offset, limit, page = _paginate(kw) total = M.search_count([]) recs = M.search([], offset=offset, limit=limit, order='id desc') items = [{ 'id': r.id, 'media_id': r.media_id.id if r.media_id else 0, 'media_name': r.media_id.name if r.media_id else '', 'student_id': r.student_id.id if hasattr(r, 'student_id') and r.student_id else 0, 'student_name': r.student_id.partner_id.name if hasattr(r, 'student_id') and r.student_id and r.student_id.partner_id else '', 'faculty_id': r.faculty_id.id if hasattr(r, 'faculty_id') and r.faculty_id else 0, 'faculty_name': r.faculty_id.partner_id.name if hasattr(r, 'faculty_id') and r.faculty_id and r.faculty_id.partner_id else '', 'issued_date': str(r.issued_date) if hasattr(r, 'issued_date') and r.issued_date else '', 'return_date': str(r.return_date) if hasattr(r, 'return_date') and r.return_date else '', 'state': getattr(r, 'state', 'issue') or 'issue', } for r in recs] return _json_response({'items': items, 'data': items, 'total': total, 'page': page, 'size': limit}) except Exception as e: return _error_response(str(e), 500) @http.route('/api/library/movements', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def create_movement(self, **kw): try: body = _get_json_body() vals = {} if body.get('media_id'): vals['media_id'] = int(body['media_id']) if body.get('student_id'): vals['student_id'] = int(body['student_id']) if body.get('faculty_id'): vals['faculty_id'] = int(body['faculty_id']) rec = request.env['op.media.movement'].sudo().create(vals) return _json_response({'data': {'id': rec.id}}) except Exception as e: return _error_response(str(e), 500) @http.route('/api/library/movements/', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False) @jwt_required def update_movement(self, mid, **kw): try: rec = request.env['op.media.movement'].sudo().browse(mid) if not rec.exists(): return _error_response('Not found', 404) body = _get_json_body() vals = {} if 'return_date' in body: vals['return_date'] = body['return_date'] if vals: rec.write(vals) return _json_response({'data': {'id': rec.id}}) except Exception as e: return _error_response(str(e), 500) @http.route('/api/library/movements//return', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def return_movement(self, mid, **kw): try: rec = request.env['op.media.movement'].sudo().browse(mid) if not rec.exists(): return _error_response('Not found', 404) from datetime import date rec.write({'return_date': str(date.today()), 'state': 'return'}) return _json_response({'data': {'id': rec.id}}) except Exception as e: return _error_response(str(e), 500) # ── Library Cards ──────────────────────────────────────────────── @http.route('/api/library/cards', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def list_cards(self, **kw): try: M = request.env['op.library.card'].sudo() offset, limit, page = _paginate(kw) total = M.search_count([]) recs = M.search([], offset=offset, limit=limit, order='id desc') items = [{ 'id': r.id, 'number': r.number if hasattr(r, 'number') else str(r.id), 'student_id': r.student_id.id if hasattr(r, 'student_id') and r.student_id else 0, 'student_name': r.student_id.partner_id.name if hasattr(r, 'student_id') and r.student_id and r.student_id.partner_id else '', 'faculty_id': r.faculty_id.id if hasattr(r, 'faculty_id') and r.faculty_id else 0, 'faculty_name': r.faculty_id.partner_id.name if hasattr(r, 'faculty_id') and r.faculty_id and r.faculty_id.partner_id else '', } for r in recs] return _json_response({'items': items, 'data': items, 'total': total, 'page': page, 'size': limit}) except Exception as e: return _error_response(str(e), 500) @http.route('/api/library/cards', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def create_card(self, **kw): try: body = _get_json_body() vals = {} if body.get('student_id'): vals['student_id'] = int(body['student_id']) rec = request.env['op.library.card'].sudo().create(vals) return _json_response({'data': {'id': rec.id}}) except Exception as e: return _error_response(str(e), 500)