Implements tester feedback against API Q1 §5.9.1.2 / §6.4.2: - Root Cause field + 6M Root Cause Category lookup (Man/Machine/Method/ Material/Measurement/Environment), separate from Deviation Detail/Category - "Corrective Action Required?" Yes/No gate on every NCR with a required justification - Corrective action plan with owner + due date; owner is notified by email - Effectiveness verification (result, notes, server-stamped verifier/date) required before an NCR can close when corrective action is required — costing returns 409 listing the missing pieces - Recurring-issue flag with bidirectional NCR-to-NCR links; prior NCRs show a warning when later NCRs reference them - Dashboard metrics: % root cause completed, % CAPA verified effective, avg CAPA close time, overdue CAPA count, NCRs by root cause category - CAPA section in the NCR detail UI, printable PDF, CSV export, and the vw_ncr_full Power BI view; admin list manager for root cause categories - Migrations 0003 (schema + seeded 6M lookup) and 0004 (view refresh); demo seed data exercises every metric Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
161 lines
5.1 KiB
Python
161 lines
5.1 KiB
Python
"""add CAPA fields to the vw_ncr_full reporting view
|
|
|
|
Revision ID: 0004
|
|
Revises: 0003
|
|
Create Date: 2026-08-04
|
|
"""
|
|
from alembic import op
|
|
|
|
revision = "0004"
|
|
down_revision = "0003"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
VW_NCR_FULL_CAPA = """
|
|
CREATE OR REPLACE VIEW vw_ncr_full AS
|
|
SELECT
|
|
n.id AS ncr_id,
|
|
n.ncr_number,
|
|
n.ncr_year,
|
|
n.ncr_seq,
|
|
n.created_at,
|
|
n.job_number,
|
|
d.name AS department,
|
|
dc.name AS deviation_category,
|
|
req.display_name AS requester,
|
|
req.email AS requester_email,
|
|
da.display_name AS disposition_authority,
|
|
n.stage,
|
|
n.stage_entered_at,
|
|
DATEDIFF(UTC_TIMESTAMP(), n.stage_entered_at) AS days_in_stage,
|
|
n.deviation_detail,
|
|
n.qc_authority,
|
|
n.work_order,
|
|
n.disposition_notes,
|
|
n.secondary_review_needed,
|
|
(SELECT GROUP_CONCAT(u2.display_name ORDER BY u2.display_name SEPARATOR '; ')
|
|
FROM ncr_secondary_assignees sa2
|
|
JOIN users u2 ON u2.id = sa2.user_id
|
|
WHERE sa2.ncr_id = n.id) AS secondary_authorities,
|
|
n.operations_complete,
|
|
n.operations_completed_at,
|
|
opu.display_name AS operations_completed_by,
|
|
n.qc_approval,
|
|
n.inspection_notes,
|
|
n.qc_closed,
|
|
n.qc_closed_at,
|
|
qcu.display_name AS qc_closed_by,
|
|
n.root_cause,
|
|
rcc.name AS root_cause_category,
|
|
n.corrective_action_required,
|
|
n.corrective_action_justification,
|
|
n.corrective_action_plan,
|
|
cao.display_name AS corrective_action_owner,
|
|
n.corrective_action_due_date,
|
|
n.corrective_action_opened_at,
|
|
n.effectiveness_result,
|
|
n.effectiveness_notes,
|
|
n.effectiveness_verified_at,
|
|
evu.display_name AS effectiveness_verified_by,
|
|
n.is_recurring,
|
|
(SELECT GROUP_CONCAT(rn.ncr_number ORDER BY rn.ncr_number SEPARATOR '; ')
|
|
FROM ncr_links nl
|
|
JOIN ncrs rn ON rn.id = nl.related_ncr_id
|
|
WHERE nl.ncr_id = n.id) AS related_ncrs,
|
|
n.labor_cost,
|
|
n.material_cost,
|
|
n.service_cost,
|
|
n.other_cost,
|
|
COALESCE(n.labor_cost, 0) + COALESCE(n.material_cost, 0)
|
|
+ COALESCE(n.service_cost, 0) + COALESCE(n.other_cost, 0) AS total_cost,
|
|
n.costing_completed_at,
|
|
n.closed_at,
|
|
clu.display_name AS closed_by,
|
|
ji.part_id,
|
|
ji.part_description,
|
|
ji.customer_name,
|
|
ji.work_order_status,
|
|
(SELECT COUNT(*) FROM attachments a WHERE a.ncr_id = n.id) AS attachment_count
|
|
FROM ncrs n
|
|
JOIN departments d ON d.id = n.department_id
|
|
JOIN deviation_categories dc ON dc.id = n.deviation_category_id
|
|
JOIN users req ON req.id = n.requester_id
|
|
JOIN users da ON da.id = n.disposition_authority_id
|
|
LEFT JOIN users opu ON opu.id = n.operations_completed_by_id
|
|
LEFT JOIN users qcu ON qcu.id = n.qc_closed_by_id
|
|
LEFT JOIN users clu ON clu.id = n.closed_by_id
|
|
LEFT JOIN root_cause_categories rcc ON rcc.id = n.root_cause_category_id
|
|
LEFT JOIN users cao ON cao.id = n.corrective_action_owner_id
|
|
LEFT JOIN users evu ON evu.id = n.effectiveness_verified_by_id
|
|
LEFT JOIN job_info ji ON ji.ncr_id = n.id
|
|
"""
|
|
|
|
|
|
# The 0002 definition, restored on downgrade.
|
|
VW_NCR_FULL_PREV = """
|
|
CREATE OR REPLACE VIEW vw_ncr_full AS
|
|
SELECT
|
|
n.id AS ncr_id,
|
|
n.ncr_number,
|
|
n.ncr_year,
|
|
n.ncr_seq,
|
|
n.created_at,
|
|
n.job_number,
|
|
d.name AS department,
|
|
dc.name AS deviation_category,
|
|
req.display_name AS requester,
|
|
req.email AS requester_email,
|
|
da.display_name AS disposition_authority,
|
|
n.stage,
|
|
n.stage_entered_at,
|
|
DATEDIFF(UTC_TIMESTAMP(), n.stage_entered_at) AS days_in_stage,
|
|
n.deviation_detail,
|
|
n.qc_authority,
|
|
n.work_order,
|
|
n.disposition_notes,
|
|
n.secondary_review_needed,
|
|
(SELECT GROUP_CONCAT(u2.display_name ORDER BY u2.display_name SEPARATOR '; ')
|
|
FROM ncr_secondary_assignees sa2
|
|
JOIN users u2 ON u2.id = sa2.user_id
|
|
WHERE sa2.ncr_id = n.id) AS secondary_authorities,
|
|
n.operations_complete,
|
|
n.operations_completed_at,
|
|
opu.display_name AS operations_completed_by,
|
|
n.qc_approval,
|
|
n.inspection_notes,
|
|
n.qc_closed,
|
|
n.qc_closed_at,
|
|
qcu.display_name AS qc_closed_by,
|
|
n.labor_cost,
|
|
n.material_cost,
|
|
n.service_cost,
|
|
n.other_cost,
|
|
COALESCE(n.labor_cost, 0) + COALESCE(n.material_cost, 0)
|
|
+ COALESCE(n.service_cost, 0) + COALESCE(n.other_cost, 0) AS total_cost,
|
|
n.costing_completed_at,
|
|
n.closed_at,
|
|
clu.display_name AS closed_by,
|
|
ji.part_id,
|
|
ji.part_description,
|
|
ji.customer_name,
|
|
ji.work_order_status,
|
|
(SELECT COUNT(*) FROM attachments a WHERE a.ncr_id = n.id) AS attachment_count
|
|
FROM ncrs n
|
|
JOIN departments d ON d.id = n.department_id
|
|
JOIN deviation_categories dc ON dc.id = n.deviation_category_id
|
|
JOIN users req ON req.id = n.requester_id
|
|
JOIN users da ON da.id = n.disposition_authority_id
|
|
LEFT JOIN users opu ON opu.id = n.operations_completed_by_id
|
|
LEFT JOIN users qcu ON qcu.id = n.qc_closed_by_id
|
|
LEFT JOIN users clu ON clu.id = n.closed_by_id
|
|
LEFT JOIN job_info ji ON ji.ncr_id = n.id
|
|
"""
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.execute(VW_NCR_FULL_CAPA)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.execute(VW_NCR_FULL_PREV)
|