changes
This commit is contained in:
@@ -5,3 +5,4 @@
|
||||
|
||||
from . import ir_actions_report
|
||||
from . import report_wo_margin
|
||||
from . import report_fp_quality_monthly
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Sub 12 Phase E — backing data computation for the Monthly Quality
|
||||
# Summary PDF.
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ReportFpQualityMonthly(models.AbstractModel):
|
||||
_name = 'report.fusion_plating_reports.report_fp_quality_monthly_doc'
|
||||
_description = 'Monthly Quality Summary — Backing'
|
||||
|
||||
@api.model
|
||||
def _get_report_values(self, docids, data=None):
|
||||
Company = self.env['res.company']
|
||||
# Default to the user's current company when called from a menu
|
||||
# action with no record selection (docids will be False/None/[]).
|
||||
companies = Company.browse(docids) if docids else self.env.company
|
||||
today = fields.Date.context_today(self.env.user)
|
||||
period_start = today.replace(day=1)
|
||||
period_label = (
|
||||
f'{period_start.strftime("%B %Y")} '
|
||||
f'(running through {today.strftime("%Y-%m-%d")})'
|
||||
)
|
||||
|
||||
Hold = self.env['fusion.plating.quality.hold']
|
||||
Check = self.env['fusion.plating.quality.check']
|
||||
Ncr = self.env['fusion.plating.ncr']
|
||||
Capa = self.env['fusion.plating.capa']
|
||||
Rma = self.env['fusion.plating.rma'] \
|
||||
if 'fusion.plating.rma' in self.env else None
|
||||
|
||||
def _bytype(model, label, opened_field, closed_field, open_dom,
|
||||
overdue_dom):
|
||||
if model is None:
|
||||
return {
|
||||
'label': label, 'opened': 0, 'closed': 0,
|
||||
'open_total': 0, 'overdue': 0,
|
||||
}
|
||||
opened = model.search_count([
|
||||
(opened_field, '>=', period_start),
|
||||
])
|
||||
closed = (
|
||||
model.search_count([(closed_field, '>=', period_start)])
|
||||
if closed_field else 0
|
||||
)
|
||||
return {
|
||||
'label': label,
|
||||
'opened': opened,
|
||||
'closed': closed,
|
||||
'open_total': model.search_count(open_dom),
|
||||
'overdue': model.search_count(overdue_dom),
|
||||
}
|
||||
|
||||
cutoff_3d = fields.Datetime.subtract(fields.Datetime.now(), days=3)
|
||||
cutoff_7d = fields.Datetime.subtract(fields.Datetime.now(), days=7)
|
||||
cutoff_5d = fields.Datetime.subtract(fields.Datetime.now(), days=5)
|
||||
cutoff_14d = fields.Datetime.subtract(fields.Datetime.now(), days=14)
|
||||
|
||||
by_type = [
|
||||
_bytype(
|
||||
Hold, 'Quality Holds',
|
||||
'create_date', None,
|
||||
[('state', 'in', ('on_hold', 'under_review'))],
|
||||
[('state', 'in', ('on_hold', 'under_review')),
|
||||
('create_date', '<', cutoff_3d)],
|
||||
),
|
||||
_bytype(
|
||||
Check, 'QC Checks',
|
||||
'create_date', None,
|
||||
[('state', '=', 'pending')] if 'state' in Check._fields else [],
|
||||
[],
|
||||
),
|
||||
_bytype(
|
||||
Ncr, 'Non-Conformance Reports',
|
||||
'reported_date', 'closed_date',
|
||||
[('state', 'in', ('open', 'containment', 'disposition'))],
|
||||
[('state', 'in', ('open', 'containment', 'disposition')),
|
||||
('reported_date', '<', cutoff_7d)],
|
||||
),
|
||||
_bytype(
|
||||
Capa, 'CAPAs',
|
||||
'create_date', None,
|
||||
[('state', 'not in', ('effective', 'closed'))],
|
||||
[('state', 'not in', ('effective', 'closed')),
|
||||
('due_date', '<', today),
|
||||
('due_date', '!=', False)],
|
||||
),
|
||||
]
|
||||
if Rma is not None:
|
||||
by_type.append(_bytype(
|
||||
Rma, 'RMAs',
|
||||
'create_date', None,
|
||||
[('state', 'not in', ('closed', 'cancelled'))],
|
||||
['|',
|
||||
'&', ('state', '=', 'received'),
|
||||
('create_date', '<', cutoff_5d),
|
||||
'&', ('state', 'in', ('authorised', 'shipped_to_us')),
|
||||
('create_date', '<', cutoff_14d)],
|
||||
))
|
||||
|
||||
# NCR severity
|
||||
ncr_severity = []
|
||||
for sev_code, sev_label in [
|
||||
('critical', 'Critical'), ('high', 'High'),
|
||||
('medium', 'Medium'), ('low', 'Low'),
|
||||
]:
|
||||
ncr_severity.append({
|
||||
'label': sev_label,
|
||||
'count': Ncr.search_count([
|
||||
('severity', '=', sev_code),
|
||||
('reported_date', '>=', period_start),
|
||||
]),
|
||||
})
|
||||
|
||||
# CAPA effectiveness
|
||||
closed_in_period = Capa.search_count([
|
||||
('state', 'in', ('effective', 'closed', 'not_effective')),
|
||||
('verification_date', '>=', period_start),
|
||||
])
|
||||
effective = Capa.search_count([
|
||||
('state', '=', 'effective'),
|
||||
('verification_date', '>=', period_start),
|
||||
])
|
||||
not_effective = Capa.search_count([
|
||||
('state', '=', 'not_effective'),
|
||||
('verification_date', '>=', period_start),
|
||||
])
|
||||
rate_pct = (
|
||||
int(round(100.0 * effective / closed_in_period))
|
||||
if closed_in_period else 0
|
||||
)
|
||||
|
||||
# Repeat customers (≥3 NCRs in last 90 days)
|
||||
cutoff_90d = today - timedelta(days=90)
|
||||
# Odoo 19 — use _read_group with aggregates=['__count'].
|
||||
groups = self.env['fusion.plating.ncr']._read_group(
|
||||
domain=[('reported_date', '>=', cutoff_90d),
|
||||
('customer_partner_id', '!=', False)],
|
||||
groupby=['customer_partner_id'],
|
||||
aggregates=['__count'],
|
||||
)
|
||||
repeat_customers = []
|
||||
for partner, count in groups:
|
||||
if count < 3:
|
||||
continue
|
||||
rma_count = (
|
||||
Rma.search_count([
|
||||
('partner_id', '=', partner.id),
|
||||
('state', 'not in', ('closed', 'cancelled')),
|
||||
]) if Rma else 0
|
||||
)
|
||||
repeat_customers.append({
|
||||
'name': partner.display_name,
|
||||
'ncr_count': count,
|
||||
'rma_count': rma_count,
|
||||
})
|
||||
repeat_customers.sort(key=lambda r: r['ncr_count'], reverse=True)
|
||||
|
||||
return {
|
||||
'doc_ids': companies.ids,
|
||||
'doc_model': 'res.company',
|
||||
'docs': companies,
|
||||
'data': {
|
||||
'period_label': period_label,
|
||||
'generated_at': fields.Datetime.now().strftime('%Y-%m-%d %H:%M'),
|
||||
'by_type': by_type,
|
||||
'ncr_severity': ncr_severity,
|
||||
'capa': {
|
||||
'closed': closed_in_period,
|
||||
'effective': effective,
|
||||
'not_effective': not_effective,
|
||||
'rate_pct': rate_pct,
|
||||
},
|
||||
'repeat_customers': repeat_customers,
|
||||
},
|
||||
}
|
||||
@@ -96,6 +96,12 @@ class ReportWoMargin(models.AbstractModel):
|
||||
# ------------------------------------------------------------------
|
||||
@api.model
|
||||
def _get_report_values(self, docids, data=None):
|
||||
# Sub 11 — MRP gone. The report is bound to fusion_plating_reports.action_report_wo_margin
|
||||
# which itself was uninstalled. Returning empty docs keeps the
|
||||
# AbstractModel safe to import (its sister fp.job report
|
||||
# report_fp_job_margin owns the live margin path now).
|
||||
if 'mrp.production' not in self.env:
|
||||
return {'doc_ids': [], 'doc_model': 'mrp.production', 'docs': []}
|
||||
productions = self.env['mrp.production'].browse(docids)
|
||||
docs = []
|
||||
for mo in productions:
|
||||
|
||||
Reference in New Issue
Block a user