changes
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import fp_qc_controller
|
||||
@@ -0,0 +1,284 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
"""HTTP endpoints for the mobile QC checklist OWL client action.
|
||||
|
||||
Kept narrow (read state + mark-pass/fail + upload PDF + finalize). The
|
||||
OWL component is purely a thin client over these endpoints so any
|
||||
future native mobile app can reuse the same API.
|
||||
"""
|
||||
import base64
|
||||
import logging
|
||||
|
||||
from odoo import http, _
|
||||
from odoo.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FpQcController(http.Controller):
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ------------------------------------------------------------------
|
||||
@staticmethod
|
||||
def _check(check_id):
|
||||
"""Resolve and access-check a QC record."""
|
||||
if not check_id:
|
||||
return False
|
||||
check = request.env['fusion.plating.quality.check'].browse(
|
||||
int(check_id)
|
||||
).exists()
|
||||
if not check:
|
||||
return False
|
||||
check.check_access('read')
|
||||
return check
|
||||
|
||||
@staticmethod
|
||||
def _line_payload(line):
|
||||
return {
|
||||
'id': line.id,
|
||||
'sequence': line.sequence,
|
||||
'name': line.name,
|
||||
'description': line.description or '',
|
||||
'check_type': line.check_type,
|
||||
'required': line.required,
|
||||
'requires_value': line.requires_value,
|
||||
'value': line.value,
|
||||
'value_min': line.value_min,
|
||||
'value_max': line.value_max,
|
||||
'value_uom': line.value_uom or '',
|
||||
'value_in_range': line.value_in_range,
|
||||
'requires_photo': line.requires_photo,
|
||||
'has_photo': bool(line.photo_attachment_id),
|
||||
'photo_attachment_id': line.photo_attachment_id.id or False,
|
||||
'result': line.result or 'pending',
|
||||
'notes': line.notes or '',
|
||||
'inspector_name': (
|
||||
line.inspector_id.name if line.inspector_id else ''
|
||||
),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _check_payload(check):
|
||||
return {
|
||||
'id': check.id,
|
||||
'name': check.name,
|
||||
'state': check.state,
|
||||
'overall_result': check.overall_result or '',
|
||||
'job_id': check.job_id.id if check.job_id else False,
|
||||
'job_name': check.job_id.name if check.job_id else '',
|
||||
'partner_name': (
|
||||
check.partner_id.name if check.partner_id else ''
|
||||
),
|
||||
'template_name': (
|
||||
check.template_id.name if check.template_id else ''
|
||||
),
|
||||
'inspector_name': (
|
||||
check.inspector_id.name if check.inspector_id else ''
|
||||
),
|
||||
'line_count': check.line_count,
|
||||
'lines_passed': check.lines_passed,
|
||||
'lines_failed': check.lines_failed,
|
||||
'lines_pending': check.lines_pending,
|
||||
'require_thickness_readings': check.require_thickness_readings,
|
||||
'require_thickness_report_pdf': check.require_thickness_report_pdf,
|
||||
'has_thickness_pdf': bool(check.thickness_report_pdf_id),
|
||||
'thickness_reading_count': check.thickness_reading_count,
|
||||
'notes': check.notes or '',
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET state — OWL calls this on mount + after every action
|
||||
# ------------------------------------------------------------------
|
||||
@http.route(
|
||||
'/fp/qc/get', type='jsonrpc', auth='user', methods=['POST'],
|
||||
)
|
||||
def get_state(self, check_id=None, job_id=None, **kw):
|
||||
check = self._check(check_id)
|
||||
if not check and job_id:
|
||||
# Resolve latest active QC for this fp.job
|
||||
check = request.env['fusion.plating.quality.check'].search([
|
||||
('job_id', '=', int(job_id)),
|
||||
], order='create_date desc', limit=1)
|
||||
if not check:
|
||||
return {'ok': False, 'error': 'no_qc'}
|
||||
if not check:
|
||||
return {'ok': False, 'error': 'not_found'}
|
||||
return {
|
||||
'ok': True,
|
||||
'check': self._check_payload(check),
|
||||
'lines': [
|
||||
self._line_payload(l)
|
||||
for l in check.line_ids.sorted('sequence')
|
||||
],
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Line actions
|
||||
# ------------------------------------------------------------------
|
||||
@http.route(
|
||||
'/fp/qc/line/mark', type='jsonrpc', auth='user', methods=['POST'],
|
||||
)
|
||||
def line_mark(self, check_id=None, line_id=None, result=None,
|
||||
value=None, notes=None, **kw):
|
||||
check = self._check(check_id)
|
||||
if not check:
|
||||
return {'ok': False, 'error': 'not_found'}
|
||||
Line = request.env['fusion.plating.quality.check.line']
|
||||
line = Line.browse(int(line_id)).exists()
|
||||
if not line or line.check_id.id != check.id:
|
||||
return {'ok': False, 'error': 'invalid_line'}
|
||||
|
||||
# Start the check if it's still draft
|
||||
if check.state == 'draft':
|
||||
check.action_start()
|
||||
|
||||
# Numeric value handling — write before action to let
|
||||
# _compute_value_in_range update the record.
|
||||
vals = {}
|
||||
if value is not None and line.requires_value:
|
||||
try:
|
||||
vals['value'] = float(value)
|
||||
except (TypeError, ValueError):
|
||||
return {'ok': False, 'error': 'invalid_value'}
|
||||
if notes is not None:
|
||||
vals['notes'] = notes
|
||||
if vals:
|
||||
line.write(vals)
|
||||
|
||||
try:
|
||||
if result == 'pass':
|
||||
line.action_mark_pass()
|
||||
elif result == 'fail':
|
||||
line.action_mark_fail()
|
||||
elif result == 'na':
|
||||
line.action_mark_na()
|
||||
elif result == 'pending':
|
||||
line.write({
|
||||
'result': 'pending',
|
||||
'inspector_id': False,
|
||||
'completed_at': False,
|
||||
})
|
||||
except Exception as e:
|
||||
return {'ok': False, 'error': str(e)}
|
||||
|
||||
return {
|
||||
'ok': True,
|
||||
'line': self._line_payload(line),
|
||||
'check': self._check_payload(check),
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Photo upload for an individual line
|
||||
# ------------------------------------------------------------------
|
||||
@http.route(
|
||||
'/fp/qc/line/photo', type='http', auth='user', methods=['POST'],
|
||||
csrf=False,
|
||||
)
|
||||
def line_photo(self, line_id=None, **kw):
|
||||
Line = request.env['fusion.plating.quality.check.line']
|
||||
line = Line.browse(int(line_id)).exists()
|
||||
if not line:
|
||||
return request.make_json_response(
|
||||
{'ok': False, 'error': 'invalid_line'},
|
||||
)
|
||||
upload = request.httprequest.files.get('file')
|
||||
if not upload:
|
||||
return request.make_json_response(
|
||||
{'ok': False, 'error': 'no_file'},
|
||||
)
|
||||
data = upload.read()
|
||||
if not data:
|
||||
return request.make_json_response(
|
||||
{'ok': False, 'error': 'empty_file'},
|
||||
)
|
||||
att = request.env['ir.attachment'].create({
|
||||
'name': upload.filename or 'qc_photo.jpg',
|
||||
'type': 'binary',
|
||||
'datas': base64.b64encode(data),
|
||||
'res_model': 'fusion.plating.quality.check.line',
|
||||
'res_id': line.id,
|
||||
'mimetype': upload.mimetype or 'image/jpeg',
|
||||
})
|
||||
line.write({'photo_attachment_id': att.id})
|
||||
return request.make_json_response({
|
||||
'ok': True,
|
||||
'attachment_id': att.id,
|
||||
})
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Fischerscope PDF upload
|
||||
# ------------------------------------------------------------------
|
||||
@http.route(
|
||||
'/fp/qc/thickness_pdf', type='http', auth='user',
|
||||
methods=['POST'], csrf=False,
|
||||
)
|
||||
def thickness_pdf(self, check_id=None, **kw):
|
||||
check = self._check(check_id)
|
||||
if not check:
|
||||
return request.make_json_response(
|
||||
{'ok': False, 'error': 'not_found'},
|
||||
)
|
||||
upload = request.httprequest.files.get('file')
|
||||
if not upload:
|
||||
return request.make_json_response(
|
||||
{'ok': False, 'error': 'no_file'},
|
||||
)
|
||||
data = upload.read()
|
||||
if not data:
|
||||
return request.make_json_response(
|
||||
{'ok': False, 'error': 'empty_file'},
|
||||
)
|
||||
att = request.env['ir.attachment'].create({
|
||||
'name': upload.filename or 'thickness_report.pdf',
|
||||
'type': 'binary',
|
||||
'datas': base64.b64encode(data),
|
||||
'res_model': 'fusion.plating.quality.check',
|
||||
'res_id': check.id,
|
||||
'mimetype': upload.mimetype or 'application/pdf',
|
||||
})
|
||||
# Triggers _on_thickness_pdf_uploaded via write() override.
|
||||
check.write({'thickness_report_pdf_id': att.id})
|
||||
return request.make_json_response({
|
||||
'ok': True,
|
||||
'attachment_id': att.id,
|
||||
'reading_count': check.thickness_reading_count,
|
||||
})
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Check-level actions
|
||||
# ------------------------------------------------------------------
|
||||
@http.route(
|
||||
'/fp/qc/finalize', type='jsonrpc', auth='user', methods=['POST'],
|
||||
)
|
||||
def finalize(self, check_id=None, result=None, notes=None, **kw):
|
||||
check = self._check(check_id)
|
||||
if not check:
|
||||
return {'ok': False, 'error': 'not_found'}
|
||||
if notes is not None:
|
||||
check.write({'notes': notes})
|
||||
try:
|
||||
if result == 'pass':
|
||||
check.action_pass()
|
||||
elif result == 'fail':
|
||||
check.action_fail()
|
||||
elif result == 'rework':
|
||||
check.action_rework()
|
||||
else:
|
||||
return {'ok': False, 'error': 'invalid_result'}
|
||||
except Exception as e:
|
||||
return {'ok': False, 'error': str(e)}
|
||||
return {'ok': True, 'check': self._check_payload(check)}
|
||||
|
||||
@http.route(
|
||||
'/fp/qc/start', type='jsonrpc', auth='user', methods=['POST'],
|
||||
)
|
||||
def start(self, check_id=None, **kw):
|
||||
check = self._check(check_id)
|
||||
if not check:
|
||||
return {'ok': False, 'error': 'not_found'}
|
||||
if check.state == 'draft':
|
||||
check.action_start()
|
||||
return {'ok': True, 'check': self._check_payload(check)}
|
||||
Reference in New Issue
Block a user