chore(plating): de-dash shipped code + intake-neutral customer emails
Replace em-dashes and en-dashes with hyphens across 789 shipped source files (py/xml/js/scss) so the delivered module reads as human-written; em-dashes had become a recognizable AI-generated tell. Internal .md dev notes are excluded. The WO-sticker mojibake strippers keep their dash search targets (now written — / –). No logic changes: comments and display strings only; validated with py_compile + lxml parse. Rewrite the 7 customer notification emails to be intake-neutral (ship-in / drop-off / pickup) and repair-aware, and fix the Shipped email documents line (packing slip vs bill of lading; certificate only when issued). Subjects use a hyphen separator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,27 +26,27 @@ from . import res_config_settings
|
||||
from . import res_partner
|
||||
from . import fp_part_catalog
|
||||
|
||||
# Phase 1 of MRP cut-out (Sub 11) — relocated from fusion_plating_bridge_mrp.
|
||||
# Phase 1 of MRP cut-out (Sub 11) - relocated from fusion_plating_bridge_mrp.
|
||||
from . import fp_qc_template
|
||||
from . import fp_thickness_reading
|
||||
from . import fp_quality_check
|
||||
|
||||
# Sub 12 Phase A — native RMA + the inverse fields it hangs off existing
|
||||
# Sub 12 Phase A - native RMA + the inverse fields it hangs off existing
|
||||
# quality and receiving models.
|
||||
from . import fp_rma
|
||||
from . import fp_rma_links
|
||||
|
||||
# Sub 12 Phase B — categorisation primitives + cross-model link fields.
|
||||
# Sub 12 Phase B - categorisation primitives + cross-model link fields.
|
||||
from . import fp_quality_tag
|
||||
from . import fp_quality_reason
|
||||
from . import fp_quality_team
|
||||
from . import fp_quality_alert_stage
|
||||
from . import fp_quality_categorisation_links
|
||||
|
||||
# Sub 12 Phase C — trigger-based quality points.
|
||||
# Sub 12 Phase C - trigger-based quality points.
|
||||
from . import fp_quality_point
|
||||
from . import fp_quality_point_hooks
|
||||
|
||||
# Sub 12 Phase D — smart-button counts + cross-creation actions.
|
||||
# Sub 12 Phase D - smart-button counts + cross-creation actions.
|
||||
from . import fp_quality_smart_buttons
|
||||
from . import fp_quality_cross_creation
|
||||
|
||||
@@ -14,7 +14,7 @@ class FpAudit(models.Model):
|
||||
relationship.
|
||||
"""
|
||||
_name = 'fusion.plating.audit'
|
||||
_description = 'Fusion Plating — Audit'
|
||||
_description = 'Fusion Plating - Audit'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'audit_date desc, id desc'
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class FpAvl(models.Model):
|
||||
in the list for traceability.
|
||||
"""
|
||||
_name = 'fusion.plating.avl'
|
||||
_description = 'Fusion Plating — Approved Vendor List'
|
||||
_description = 'Fusion Plating - Approved Vendor List'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'state, name'
|
||||
_rec_name = 'name'
|
||||
@@ -72,7 +72,7 @@ class FpAvl(models.Model):
|
||||
)
|
||||
approved_for = fields.Char(
|
||||
string='Approved For',
|
||||
help='Free text — what processes / products / services this vendor '
|
||||
help='Free text - what processes / products / services this vendor '
|
||||
'is approved to supply.',
|
||||
)
|
||||
notes = fields.Html(
|
||||
|
||||
@@ -17,7 +17,7 @@ class FpCalibrationEquipment(models.Model):
|
||||
fusion.plating.calibration.event.
|
||||
"""
|
||||
_name = 'fusion.plating.calibration.equipment'
|
||||
_description = 'Fusion Plating — Calibrated Equipment'
|
||||
_description = 'Fusion Plating - Calibrated Equipment'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'next_cal_date asc, name'
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class FpCalibrationEvent(models.Model):
|
||||
which jobs may have been measured by an out-of-tolerance instrument.
|
||||
"""
|
||||
_name = 'fusion.plating.calibration.event'
|
||||
_description = 'Fusion Plating — Calibration Event'
|
||||
_description = 'Fusion Plating - Calibration Event'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'cal_date desc, id desc'
|
||||
_rec_name = 'display_name'
|
||||
@@ -88,6 +88,6 @@ class FpCalibrationEvent(models.Model):
|
||||
def _compute_display_name(self):
|
||||
for rec in self:
|
||||
if rec.equipment_id and rec.cal_date:
|
||||
rec.display_name = f'{rec.equipment_id.code} — {rec.cal_date}'
|
||||
rec.display_name = f'{rec.equipment_id.code} - {rec.cal_date}'
|
||||
else:
|
||||
rec.display_name = 'Calibration Event'
|
||||
|
||||
@@ -15,7 +15,7 @@ class FpCapa(models.Model):
|
||||
action plan, and an effectiveness verification step.
|
||||
"""
|
||||
_name = 'fusion.plating.capa'
|
||||
_description = 'Fusion Plating — Corrective / Preventive Action'
|
||||
_description = 'Fusion Plating - Corrective / Preventive Action'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin', 'fp.parent.numbered.mixin']
|
||||
_order = 'due_date asc, id desc'
|
||||
|
||||
@@ -108,7 +108,7 @@ class FpCapa(models.Model):
|
||||
def _fp_parent_sale_order(self):
|
||||
# CAPA usually flows from an NCR. If the NCR has a job-back-link
|
||||
# (added by future modules), we can reach SO through it. For now
|
||||
# there's no link in core — falls back to legacy seq.
|
||||
# there's no link in core - falls back to legacy seq.
|
||||
return self.env['sale.order']
|
||||
|
||||
def _fp_name_prefix(self):
|
||||
@@ -191,7 +191,7 @@ class FpCapa(models.Model):
|
||||
def action_close(self):
|
||||
"""Block close unless root_cause + action_plan + verification are set.
|
||||
|
||||
A CAPA without these is just an open ticket — the AS9100 §10.2
|
||||
A CAPA without these is just an open ticket - the AS9100 §10.2
|
||||
/ Nadcap loop requires evidence of the root cause analysis,
|
||||
the corrective/preventive action plan, AND that effectiveness
|
||||
was verified before the loop is closed.
|
||||
@@ -214,11 +214,11 @@ class FpCapa(models.Model):
|
||||
missing.append(_('Verification (date + verifier)'))
|
||||
if rec.is_effective is False and is_empty_html(rec.effectiveness_notes):
|
||||
# If marked not-effective, demand a note explaining the
|
||||
# follow-up plan — otherwise the loop never actually closes.
|
||||
# follow-up plan - otherwise the loop never actually closes.
|
||||
missing.append(_('Effectiveness Notes (required when "Not Effective")'))
|
||||
if missing:
|
||||
raise UserError(_(
|
||||
'Cannot close CAPA "%(name)s" — these fields must be '
|
||||
'Cannot close CAPA "%(name)s" - these fields must be '
|
||||
'filled in first:\n • %(fields)s\n\n'
|
||||
'A CAPA without root cause + action plan + verified '
|
||||
'effectiveness fails AS9100 §10.2 / Nadcap on audit.'
|
||||
|
||||
@@ -82,7 +82,7 @@ class FpContractReview(models.Model):
|
||||
qty = fields.Integer(string='Qty')
|
||||
due_date = fields.Date(string='Due')
|
||||
|
||||
# ---- Section 2.0 — Planning / Production Review (Planning Review) ------
|
||||
# ---- Section 2.0 - Planning / Production Review (Planning Review) ------
|
||||
|
||||
s20_acceptable_lead_time = fields.Boolean(string='Acceptable Lead Time')
|
||||
s20_capacity_to_process = fields.Boolean(string='Capacity to Process')
|
||||
@@ -121,7 +121,7 @@ class FpContractReview(models.Model):
|
||||
copy=False,
|
||||
)
|
||||
|
||||
# ---- Section 3.0 — Quality Review (QA Review) --------------------------
|
||||
# ---- Section 3.0 - Quality Review (QA Review) --------------------------
|
||||
|
||||
s30_source_control_docs = fields.Boolean(string="Source Control Documents (Customer Spec's)")
|
||||
s30_quality_clauses_supplied = fields.Boolean(string='Quality Clause(s) supplied')
|
||||
@@ -137,19 +137,19 @@ class FpContractReview(models.Model):
|
||||
s30_accepted = fields.Boolean(string='Accepted (Section 3.0)')
|
||||
s30_evaluate_risk = fields.Boolean(string='Evaluate Risk (Section 3.0)')
|
||||
s30_risk_consequence = fields.Selection(
|
||||
[('1', '1 — Minimal'),
|
||||
('2', '2 — Moderate'),
|
||||
('3', '3 — Mod. / Applicable'),
|
||||
('4', '4 — Major / Changes'),
|
||||
('5', '5 — Unacceptable')],
|
||||
[('1', '1 - Minimal'),
|
||||
('2', '2 - Moderate'),
|
||||
('3', '3 - Mod. / Applicable'),
|
||||
('4', '4 - Major / Changes'),
|
||||
('5', '5 - Unacceptable')],
|
||||
string='Consequence',
|
||||
)
|
||||
s30_risk_likelihood = fields.Selection(
|
||||
[('1', '1 — Not Likely'),
|
||||
('2', '2 — Low Likelihood'),
|
||||
('3', '3 — Likely'),
|
||||
('4', '4 — Highly Likely'),
|
||||
('5', '5 — Near Certainty')],
|
||||
[('1', '1 - Not Likely'),
|
||||
('2', '2 - Low Likelihood'),
|
||||
('3', '3 - Likely'),
|
||||
('4', '4 - Highly Likely'),
|
||||
('5', '5 - Near Certainty')],
|
||||
string='Likelihood',
|
||||
)
|
||||
s30_risk_band = fields.Selection(
|
||||
@@ -193,14 +193,14 @@ class FpContractReview(models.Model):
|
||||
tracking=True,
|
||||
)
|
||||
|
||||
# ---- "Failed QA — Awaiting Client Info" workflow ------------------------
|
||||
# ---- "Failed QA - Awaiting Client Info" workflow ------------------------
|
||||
# When a QA Signer (Brett or whoever the company has rostered) finds a
|
||||
# client requirement that fails during the QA Review, they mark the
|
||||
# review failed. The state moves to `awaiting_info`, an activity is
|
||||
# scheduled for every QA Signer to follow up, and a smart button on
|
||||
# the form gives them a one-click email composer to ping the client.
|
||||
# When the client replies, the QA Signer captures notes in
|
||||
# `special_instructions` and marks complete — the notes print on the
|
||||
# `special_instructions` and marks complete - the notes print on the
|
||||
# final QA-005 PDF for the audit trail.
|
||||
qa_failure_reason = fields.Html(
|
||||
string='QA Failure Reason',
|
||||
@@ -232,7 +232,7 @@ class FpContractReview(models.Model):
|
||||
)
|
||||
client_email_count = fields.Integer(
|
||||
compute='_compute_client_email_count',
|
||||
help='Smart-button counter — number of emails posted to chatter '
|
||||
help='Smart-button counter - number of emails posted to chatter '
|
||||
'against this review. Always non-zero after the first send.',
|
||||
)
|
||||
|
||||
@@ -257,7 +257,7 @@ class FpContractReview(models.Model):
|
||||
def _compute_name(self):
|
||||
for rec in self:
|
||||
if rec.part_id:
|
||||
rec.name = _('QA-005 — %(pn)s Rev %(rev)s') % {
|
||||
rec.name = _('QA-005 - %(pn)s Rev %(rev)s') % {
|
||||
'pn': rec.part_id.part_number or _('(no part#)'),
|
||||
'rev': rec.part_id.revision or _('(no rev)'),
|
||||
}
|
||||
@@ -313,7 +313,7 @@ class FpContractReview(models.Model):
|
||||
return True
|
||||
|
||||
# Checklist fields per section, for the "Check All" / "Clear All"
|
||||
# bulk-toggle buttons. Only the checklist boxes are flipped —
|
||||
# bulk-toggle buttons. Only the checklist boxes are flipped -
|
||||
# outcome fields (Accepted, Evaluate Risk, Risk Level / Matrix,
|
||||
# Mitigation Plan Required) remain under the user's explicit
|
||||
# decision so they don't get accidentally ticked.
|
||||
@@ -347,7 +347,7 @@ class FpContractReview(models.Model):
|
||||
self.ensure_one()
|
||||
if self[locked_field]:
|
||||
raise UserError(_(
|
||||
'Section is already signed — checklist is locked.'
|
||||
'Section is already signed - checklist is locked.'
|
||||
))
|
||||
self.write({f: value for f in fields_tuple})
|
||||
return True
|
||||
@@ -405,7 +405,7 @@ class FpContractReview(models.Model):
|
||||
'fusion_plating_quality.action_report_contract_review'
|
||||
).report_action(self)
|
||||
|
||||
# ---- "Failed QA — Awaiting Client Info" workflow ------------------------
|
||||
# ---- "Failed QA - Awaiting Client Info" workflow ------------------------
|
||||
def action_mark_qa_failed(self):
|
||||
"""QA Signer marks the review failed because a client requirement
|
||||
is missing or unclear. Captures the reason, flips state to
|
||||
@@ -418,13 +418,13 @@ class FpContractReview(models.Model):
|
||||
'Only a review at the QA Review (or Planning Review) stage '
|
||||
'can be flagged as failed. Current state: %s.'
|
||||
) % dict(self._fields['state'].selection).get(self.state, self.state))
|
||||
# Reuse the section-30 signer roster — the same group of people
|
||||
# Reuse the section-30 signer roster - the same group of people
|
||||
# who can sign QA can flag a QA failure.
|
||||
self._check_signer(30)
|
||||
if not self.qa_failure_reason or not self.qa_failure_reason.strip():
|
||||
raise UserError(_(
|
||||
'Capture the QA Failure Reason before flagging the '
|
||||
'review failed — the reason pre-fills the client email '
|
||||
'review failed - the reason pre-fills the client email '
|
||||
'and is required for the audit trail.'
|
||||
))
|
||||
self.write({'state': 'awaiting_info'})
|
||||
@@ -449,7 +449,7 @@ class FpContractReview(models.Model):
|
||||
for user in signers:
|
||||
self.activity_schedule(
|
||||
activity_type_id=activity_type.id if activity_type else False,
|
||||
summary=_('Follow up on QA-005 — client info required'),
|
||||
summary=_('Follow up on QA-005 - client info required'),
|
||||
note=self.qa_failure_reason or '',
|
||||
user_id=user.id,
|
||||
date_deadline=fields.Date.context_today(self) +
|
||||
@@ -458,14 +458,14 @@ class FpContractReview(models.Model):
|
||||
return True
|
||||
|
||||
def action_open_client_email_wizard(self):
|
||||
"""Smart-button target — opens the email composer wizard pre-filled
|
||||
"""Smart-button target - opens the email composer wizard pre-filled
|
||||
with the customer's contact email + a body templated from the
|
||||
QA failure reason. The wizard handles the actual mail.mail send
|
||||
and stamps `info_requested_date` on this review."""
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Email Client — Request Info'),
|
||||
'name': _('Email Client - Request Info'),
|
||||
'res_model': 'fp.contract.review.client.email.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
@@ -479,12 +479,12 @@ class FpContractReview(models.Model):
|
||||
}
|
||||
|
||||
def action_view_client_emails(self):
|
||||
"""Drill-down behind the smart button counter — shows the chatter
|
||||
"""Drill-down behind the smart button counter - shows the chatter
|
||||
messages of type=email for this review."""
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Client Emails — %s') % self.name,
|
||||
'name': _('Client Emails - %s') % self.name,
|
||||
'res_model': 'mail.message',
|
||||
'view_mode': 'list,form',
|
||||
'domain': [
|
||||
@@ -520,7 +520,7 @@ class FpContractReview(models.Model):
|
||||
# everyone's inbox once the case is closed.
|
||||
self.activity_feedback(
|
||||
['mail.mail_activity_data_todo'],
|
||||
feedback=_('Client info received — review closed.'),
|
||||
feedback=_('Client info received - review closed.'),
|
||||
)
|
||||
self.message_post(body=Markup(_(
|
||||
'<b>QA Review completed</b> by %(user)s after receiving '
|
||||
|
||||
@@ -11,11 +11,11 @@ class FpCustomerSpec(models.Model):
|
||||
|
||||
Holds the metadata about a specification (industry, customer, or
|
||||
internal) so jobs and process types can reference it. The actual
|
||||
document lives at document_url — could be a SharePoint link, a
|
||||
document lives at document_url - could be a SharePoint link, a
|
||||
Google Drive URL, or any other location the shop already uses.
|
||||
"""
|
||||
_name = 'fusion.plating.customer.spec'
|
||||
_description = 'Fusion Plating — Customer Specification'
|
||||
_description = 'Fusion Plating - Customer Specification'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'spec_type, code, revision desc'
|
||||
_rec_name = 'display_name'
|
||||
@@ -81,7 +81,7 @@ class FpCustomerSpec(models.Model):
|
||||
domain="[('node_type', '=', 'recipe'), ('parent_id', '=', False)]",
|
||||
string='Applicable Recipes',
|
||||
help='Recipes that can produce work to this specification. '
|
||||
'Many-to-many — one spec can cover multiple processes; '
|
||||
'Many-to-many - one spec can cover multiple processes; '
|
||||
'one recipe can satisfy multiple specs.',
|
||||
)
|
||||
print_on_cert = fields.Boolean(
|
||||
@@ -112,5 +112,5 @@ class FpCustomerSpec(models.Model):
|
||||
if rec.revision:
|
||||
parts.append(f'Rev {rec.revision}')
|
||||
if rec.name:
|
||||
parts.append(f'— {rec.name}')
|
||||
parts.append(f'- {rec.name}')
|
||||
rec.display_name = ' '.join(p for p in parts if p)
|
||||
|
||||
@@ -28,10 +28,10 @@ class FpDirectOrderLine(models.Model):
|
||||
Priority (first non-empty result wins, matches the same
|
||||
"remember last entered" pattern used by process / unit_price /
|
||||
tax / thickness in the base wizard line):
|
||||
1. What the operator already typed on this line — never clobber
|
||||
1. What the operator already typed on this line - never clobber
|
||||
2. Most recent SO line for (part, customer) where a spec
|
||||
was set — the "repeat order" carry-over
|
||||
3. Part's stored default — x_fc_default_customer_spec_id
|
||||
was set - the "repeat order" carry-over
|
||||
3. Part's stored default - x_fc_default_customer_spec_id
|
||||
"""
|
||||
for rec in self:
|
||||
if not rec.part_catalog_id or rec.customer_spec_id:
|
||||
@@ -67,7 +67,7 @@ class FpDirectOrderWizard(models.Model):
|
||||
pair wizard lines to SO lines by sequence and patch.
|
||||
|
||||
The push-to-defaults block mirrors the base wizard's thickness
|
||||
push (action_create_order's "6. Push-to-defaults" loop) — spec
|
||||
push (action_create_order's "6. Push-to-defaults" loop) - spec
|
||||
lives here in the quality module so the back-write lives here
|
||||
too. Only fills when the part default is currently empty so we
|
||||
never clobber an existing default that the part-form user set
|
||||
@@ -84,7 +84,7 @@ class FpDirectOrderWizard(models.Model):
|
||||
for wiz_line, so_line in zip(wiz_lines, so_lines):
|
||||
if wiz_line.customer_spec_id and not so_line.x_fc_customer_spec_id:
|
||||
so_line.x_fc_customer_spec_id = wiz_line.customer_spec_id.id
|
||||
# Spec push-to-default — only on first-time parts that
|
||||
# Spec push-to-default - only on first-time parts that
|
||||
# had the toggle auto-ticked (or manually ticked). Skip
|
||||
# one-off parts and parts that already have a default.
|
||||
if (wiz_line.push_to_defaults
|
||||
|
||||
@@ -15,7 +15,7 @@ class FpDocControl(models.Model):
|
||||
on this revision (a common audit ask).
|
||||
"""
|
||||
_name = 'fusion.plating.doc.control'
|
||||
_description = 'Fusion Plating — Controlled Document'
|
||||
_description = 'Fusion Plating - Controlled Document'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'doc_type, name, revision desc'
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class FpFair(models.Model):
|
||||
PPAP needs a FAIR on file.
|
||||
"""
|
||||
_name = 'fusion.plating.fair'
|
||||
_description = 'Fusion Plating — First Article Inspection Report'
|
||||
_description = 'Fusion Plating - First Article Inspection Report'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'performed_date desc, id desc'
|
||||
|
||||
|
||||
@@ -11,12 +11,12 @@ class FpNcr(models.Model):
|
||||
"""Non-Conformance Report.
|
||||
|
||||
The NCR is the entry point of the Fusion Plating QMS. Anything that
|
||||
falls outside of spec — a chemistry deviation, a customer return, an
|
||||
inspection failure, an audit observation — is opened as an NCR and
|
||||
falls outside of spec - a chemistry deviation, a customer return, an
|
||||
inspection failure, an audit observation - is opened as an NCR and
|
||||
walked through containment, disposition, and closure.
|
||||
"""
|
||||
_name = 'fusion.plating.ncr'
|
||||
_description = 'Fusion Plating — Non-Conformance Report'
|
||||
_description = 'Fusion Plating - Non-Conformance Report'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin', 'fp.parent.numbered.mixin']
|
||||
_order = 'reported_date desc, id desc'
|
||||
|
||||
@@ -132,7 +132,7 @@ class FpNcr(models.Model):
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Parent-numbered mixin hooks
|
||||
# NCRs don't have a direct SO/job link in core yet — falls back to
|
||||
# NCRs don't have a direct SO/job link in core yet - falls back to
|
||||
# legacy fusion.plating.ncr sequence. When a future module adds a
|
||||
# link, it can override _fp_parent_sale_order to enable parent
|
||||
# naming retroactively without further changes here.
|
||||
@@ -211,7 +211,7 @@ class FpNcr(models.Model):
|
||||
missing.append(_('Disposition (use-as-is / rework / scrap / RTV)'))
|
||||
if missing:
|
||||
raise UserError(_(
|
||||
'Cannot close NCR "%(name)s" — these fields must be '
|
||||
'Cannot close NCR "%(name)s" - these fields must be '
|
||||
'filled in first:\n • %(fields)s\n\n'
|
||||
'AS9100 / Nadcap auditors will reject a closed NCR '
|
||||
'that doesn\'t document what happened, why, and how '
|
||||
|
||||
@@ -56,7 +56,7 @@ class FpPartCatalog(models.Model):
|
||||
def _compute_has_confirmed_mo(self):
|
||||
"""True if this part is referenced by at least one live fp.job.
|
||||
|
||||
Sub 11 — replaced mrp.production lookup with fp.job. Trace:
|
||||
Sub 11 - replaced mrp.production lookup with fp.job. Trace:
|
||||
fp.part.catalog → sale.order.line (x_fc_part_catalog_id) →
|
||||
sale.order → fp.job (via origin name match).
|
||||
"""
|
||||
@@ -96,7 +96,7 @@ class FpPartCatalog(models.Model):
|
||||
and not completed and not in_production
|
||||
)
|
||||
|
||||
# ---- Create override — auto-stage + alert -------------------------------
|
||||
# ---- Create override - auto-stage + alert -------------------------------
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
@@ -104,7 +104,7 @@ class FpPartCatalog(models.Model):
|
||||
part is added under a customer that has contract-review
|
||||
enforcement enabled (res.partner.x_fc_contract_review_required).
|
||||
|
||||
Fires only on .create() — write/update flows never re-trigger
|
||||
Fires only on .create() - write/update flows never re-trigger
|
||||
the alert. Existing parts in the system are unaffected.
|
||||
|
||||
Three surfaces, in increasing persistence:
|
||||
@@ -168,7 +168,7 @@ class FpPartCatalog(models.Model):
|
||||
)
|
||||
customer_label = part.partner_id.display_name
|
||||
|
||||
# 1) Persistent activity — sits in the user's Activities
|
||||
# 1) Persistent activity - sits in the user's Activities
|
||||
# inbox + shows on the part record's chatter clock until
|
||||
# the user marks it done.
|
||||
if activity_type and model_id:
|
||||
@@ -197,14 +197,14 @@ class FpPartCatalog(models.Model):
|
||||
part.id, exc_info=True,
|
||||
)
|
||||
|
||||
# 2) Sticky warning toast — visible immediately to the
|
||||
# 2) Sticky warning toast - visible immediately to the
|
||||
# user who created the part. Doesn't auto-dismiss.
|
||||
try:
|
||||
Bus._sendone(
|
||||
self.env.user.partner_id,
|
||||
'simple_notification',
|
||||
{
|
||||
'title': _('Contract Review Required — %s')
|
||||
'title': _('Contract Review Required - %s')
|
||||
% part_label,
|
||||
'message': _(
|
||||
'Customer %(c)s requires a Contract Review '
|
||||
|
||||
@@ -11,9 +11,9 @@ class FpPricingRule(models.Model):
|
||||
|
||||
Lives in fusion_plating_quality because fusion.plating.customer.spec
|
||||
lives here. Rules can now match on:
|
||||
- customer_spec_id (most specific — e.g. "AMS 2404 surcharge")
|
||||
- recipe_id (recipe-tier — e.g. "EN Mid-Phos $X/sqft")
|
||||
- both blank (fallback — material/cert-level matching)
|
||||
- customer_spec_id (most specific - e.g. "AMS 2404 surcharge")
|
||||
- recipe_id (recipe-tier - e.g. "EN Mid-Phos $X/sqft")
|
||||
- both blank (fallback - material/cert-level matching)
|
||||
|
||||
The configurator's matcher is extended in fp_quote_configurator_inherit.
|
||||
"""
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Phase 1 (Sub 11) — relocated from fusion_plating_bridge_mrp.
|
||||
# Phase 1 (Sub 11) - relocated from fusion_plating_bridge_mrp.
|
||||
# This model never had MRP fields; the bridge module was just its
|
||||
# initial home. Now lives under fusion_plating_jobs.
|
||||
"""QC Checklist Template — admin config for per-customer QC requirements.
|
||||
"""QC Checklist Template - admin config for per-customer QC requirements.
|
||||
|
||||
Customers differ wildly in what they expect from quality control:
|
||||
* commercial job-shop accounts often just want "did it plate?" — one
|
||||
* commercial job-shop accounts often just want "did it plate?" - one
|
||||
visual check
|
||||
* aerospace / Nadcap customers expect visual, dimensional,
|
||||
adhesion, and Fischerscope thickness readings — every part, every
|
||||
adhesion, and Fischerscope thickness readings - every part, every
|
||||
lot, signed off
|
||||
* internal rework jobs may have no QC requirement at all
|
||||
|
||||
Rather than coding that policy into the shop, each customer gets their
|
||||
own checklist template. On job confirm, the active template is cloned
|
||||
into a fresh `fusion.plating.quality.check` — the instance operators
|
||||
into a fresh `fusion.plating.quality.check` - the instance operators
|
||||
actually fill in.
|
||||
"""
|
||||
from odoo import api, fields, models, _
|
||||
@@ -26,14 +26,14 @@ from odoo import api, fields, models, _
|
||||
|
||||
class FpQcChecklistTemplate(models.Model):
|
||||
_name = 'fp.qc.checklist.template'
|
||||
_description = 'Fusion Plating — QC Checklist Template'
|
||||
_description = 'Fusion Plating - QC Checklist Template'
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'partner_id, sequence, name'
|
||||
|
||||
name = fields.Char(
|
||||
string='Template Name', required=True, tracking=True,
|
||||
help='e.g. "Standard Aerospace CoC + Thickness" or '
|
||||
'"Commercial — Visual Only".',
|
||||
'"Commercial - Visual Only".',
|
||||
)
|
||||
sequence = fields.Integer(default=10)
|
||||
active = fields.Boolean(default=True)
|
||||
@@ -46,7 +46,7 @@ class FpQcChecklistTemplate(models.Model):
|
||||
)
|
||||
notes = fields.Html(
|
||||
string='Notes',
|
||||
help='Context for QC inspectors — what this customer cares '
|
||||
help='Context for QC inspectors - what this customer cares '
|
||||
'about, common reject reasons, spec docs to reference.',
|
||||
)
|
||||
|
||||
@@ -107,7 +107,7 @@ class FpQcChecklistTemplate(models.Model):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('QC Checks — %s') % self.name,
|
||||
'name': _('QC Checks - %s') % self.name,
|
||||
'res_model': 'fusion.plating.quality.check',
|
||||
'view_mode': 'list,form',
|
||||
'domain': [('template_id', '=', self.id)],
|
||||
@@ -117,7 +117,7 @@ class FpQcChecklistTemplate(models.Model):
|
||||
|
||||
class FpQcChecklistTemplateLine(models.Model):
|
||||
_name = 'fp.qc.checklist.template.line'
|
||||
_description = 'Fusion Plating — QC Checklist Template Line'
|
||||
_description = 'Fusion Plating - QC Checklist Template Line'
|
||||
_order = 'sequence, id'
|
||||
|
||||
template_id = fields.Many2one(
|
||||
@@ -128,7 +128,7 @@ class FpQcChecklistTemplateLine(models.Model):
|
||||
name = fields.Char(
|
||||
string='Check Item', required=True, translate=True,
|
||||
help='The operator-facing question, e.g. "No visible pits or '
|
||||
'blemishes on surface", "Thickness within 0.0005–0.0010".',
|
||||
'blemishes on surface", "Thickness within 0.0005-0.0010".',
|
||||
)
|
||||
description = fields.Text(
|
||||
string='Inspection Guidance',
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Sub 12 Phase B — quality alert stage.
|
||||
# Sub 12 Phase B - quality alert stage.
|
||||
#
|
||||
# Shared kanban-stage namespace used by both NCR and RMA. Each model has
|
||||
# its own state Selection (state machine guards) AND a stage_id Many2one
|
||||
# (kanban-draggable). The two stay in sync — see fp_quality_categorisation_links.
|
||||
# (kanban-draggable). The two stay in sync - see fp_quality_categorisation_links.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class FpQualityAlertStage(models.Model):
|
||||
_name = 'fp.quality.alert.stage'
|
||||
_description = 'Fusion Plating — Quality Alert Stage'
|
||||
_description = 'Fusion Plating - Quality Alert Stage'
|
||||
_order = 'sequence, id'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Sub 12 Phase B — categorisation field extensions.
|
||||
# Sub 12 Phase B - categorisation field extensions.
|
||||
#
|
||||
# Adds the cross-cutting tag_ids / reason_id / team_id fields to all five
|
||||
# quality records (NCR, CAPA, Hold, Check, RMA). Adds stage_id (kanban
|
||||
@@ -77,7 +77,7 @@ class FpNcrCategorisation(models.Model):
|
||||
new_state = NCR_STAGE_CODE_TO_STATE.get(rec.stage_id.code)
|
||||
if new_state and new_state != rec.state:
|
||||
# Use direct write to avoid the action_close UserError
|
||||
# guards — kanban drag is an explicit user intent.
|
||||
# guards - kanban drag is an explicit user intent.
|
||||
super(FpNcrCategorisation, rec).write({'state': new_state})
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Phase 1 (Sub 11) — relocated from fusion_plating_bridge_mrp.
|
||||
# Phase 1 (Sub 11) - relocated from fusion_plating_bridge_mrp.
|
||||
# Now binds to fp.job (native) instead of mrp.production.
|
||||
"""Per-job QC instance.
|
||||
|
||||
When a plating job confirms and the customer requires QC, we clone
|
||||
the active checklist template into a `fusion.plating.quality.check`
|
||||
with one line per template line. The inspector picks it up on the
|
||||
tablet, walks the checks, and signs off — which unblocks the job's
|
||||
tablet, walks the checks, and signs off - which unblocks the job's
|
||||
`button_mark_done`.
|
||||
|
||||
The QC also owns the Fischerscope / XDAL 600 thickness report PDF.
|
||||
@@ -33,7 +33,7 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
class FpQualityCheck(models.Model):
|
||||
_name = 'fusion.plating.quality.check'
|
||||
_description = 'Fusion Plating — Quality Check'
|
||||
_description = 'Fusion Plating - Quality Check'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'create_date desc'
|
||||
|
||||
@@ -71,7 +71,7 @@ class FpQualityCheck(models.Model):
|
||||
overall_result = fields.Selection(
|
||||
[('pass', 'Pass'), ('fail', 'Fail'), ('partial', 'Partial Pass')],
|
||||
string='Result', tracking=True,
|
||||
help='Summary outcome — set when inspector signs off.',
|
||||
help='Summary outcome - set when inspector signs off.',
|
||||
)
|
||||
line_ids = fields.One2many(
|
||||
'fusion.plating.quality.check.line', 'check_id',
|
||||
@@ -207,7 +207,7 @@ class FpQualityCheck(models.Model):
|
||||
'result': 'pending',
|
||||
})
|
||||
job.message_post(
|
||||
body=_('QC checklist "%s" created — %d items to inspect.') % (
|
||||
body=_('QC checklist "%s" created - %d items to inspect.') % (
|
||||
template.name, len(template.line_ids),
|
||||
),
|
||||
)
|
||||
@@ -233,16 +233,16 @@ class FpQualityCheck(models.Model):
|
||||
'inspector_id': self.env.user.id,
|
||||
})
|
||||
rec.message_post(body=Markup(
|
||||
'<b>QC PASSED</b> — inspector %s.'
|
||||
'<b>QC PASSED</b> - inspector %s.'
|
||||
) % self.env.user.name)
|
||||
|
||||
def action_fail(self):
|
||||
"""Mark QC failed AND auto-spawn a fusion.plating.quality.hold
|
||||
so the parts have an AS9100 disposition record. Without this
|
||||
spawning the parts are in limbo — operator can't ship and
|
||||
spawning the parts are in limbo - operator can't ship and
|
||||
nothing tracks scrap/rework/use-as-is decisions.
|
||||
|
||||
v19.0.4.x — Hold auto-spawn added per the tablet usability pass.
|
||||
v19.0.4.x - Hold auto-spawn added per the tablet usability pass.
|
||||
"""
|
||||
Hold = self.env.get('fusion.plating.quality.hold')
|
||||
for rec in self:
|
||||
@@ -253,7 +253,7 @@ class FpQualityCheck(models.Model):
|
||||
'inspector_id': self.env.user.id,
|
||||
})
|
||||
rec.message_post(body=Markup(
|
||||
'<b>QC FAILED</b> — inspector %s.'
|
||||
'<b>QC FAILED</b> - inspector %s.'
|
||||
) % self.env.user.name)
|
||||
# Auto-spawn the Hold (best-effort; QC failure stands even
|
||||
# if Hold creation fails for some odd reason).
|
||||
@@ -342,7 +342,7 @@ class FpQualityCheck(models.Model):
|
||||
)
|
||||
if pending:
|
||||
raise UserError(_(
|
||||
'Cannot pass QC "%(name)s" — %(n)d required check '
|
||||
'Cannot pass QC "%(name)s" - %(n)d required check '
|
||||
'item(s) still pending:\n • %(items)s'
|
||||
) % {
|
||||
'name': rec.name,
|
||||
@@ -352,7 +352,7 @@ class FpQualityCheck(models.Model):
|
||||
failed = rec.line_ids.filtered(lambda l: l.result == 'fail')
|
||||
if failed:
|
||||
raise UserError(_(
|
||||
'Cannot pass QC "%(name)s" — %(n)d check item(s) '
|
||||
'Cannot pass QC "%(name)s" - %(n)d check item(s) '
|
||||
'failed. Fail the QC instead, or reset those '
|
||||
'items to pass.'
|
||||
) % {'name': rec.name, 'n': len(failed)})
|
||||
@@ -418,7 +418,7 @@ class FpQualityCheck(models.Model):
|
||||
return result.stdout or ''
|
||||
except FileNotFoundError:
|
||||
_logger.warning(
|
||||
'pdftotext not installed — cannot auto-extract '
|
||||
'pdftotext not installed - cannot auto-extract '
|
||||
'Fischerscope PDF data. Install poppler-utils on '
|
||||
'the Odoo host.',
|
||||
)
|
||||
@@ -493,7 +493,7 @@ class FpQualityCheck(models.Model):
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'fp_qc_checklist',
|
||||
'name': _('QC — %s') % (self.job_id.name or ''),
|
||||
'name': _('QC - %s') % (self.job_id.name or ''),
|
||||
'params': {'check_id': self.id},
|
||||
'target': 'current',
|
||||
}
|
||||
@@ -501,7 +501,7 @@ class FpQualityCheck(models.Model):
|
||||
|
||||
class FpQualityCheckLine(models.Model):
|
||||
_name = 'fusion.plating.quality.check.line'
|
||||
_description = 'Fusion Plating — Quality Check Line'
|
||||
_description = 'Fusion Plating - Quality Check Line'
|
||||
_order = 'sequence, id'
|
||||
|
||||
check_id = fields.Many2one(
|
||||
@@ -563,8 +563,8 @@ class FpQualityCheckLine(models.Model):
|
||||
for rec in self:
|
||||
if rec.requires_value and not rec.value_in_range:
|
||||
raise UserError(_(
|
||||
'Cannot pass "%(item)s" — value %(val)s is outside '
|
||||
'the acceptance range (%(min)s – %(max)s %(uom)s).'
|
||||
'Cannot pass "%(item)s" - value %(val)s is outside '
|
||||
'the acceptance range (%(min)s - %(max)s %(uom)s).'
|
||||
) % {
|
||||
'item': rec.name,
|
||||
'val': rec.value,
|
||||
@@ -574,7 +574,7 @@ class FpQualityCheckLine(models.Model):
|
||||
})
|
||||
if rec.requires_photo and not rec.photo_attachment_id:
|
||||
raise UserError(_(
|
||||
'Cannot pass "%(item)s" — a photo is required.'
|
||||
'Cannot pass "%(item)s" - a photo is required.'
|
||||
) % {'item': rec.name})
|
||||
rec.write({
|
||||
'result': 'pass',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Sub 12 Phase D — cross-creation actions and CAPA closure-loop linkage.
|
||||
# Sub 12 Phase D - cross-creation actions and CAPA closure-loop linkage.
|
||||
#
|
||||
# - NCR.action_spawn_capa: creates a draft CAPA pre-filled from the NCR.
|
||||
# - CAPA.action_mark_not_effective override: auto-creates a follow-up NCR
|
||||
@@ -131,7 +131,7 @@ class FpCapaCrossCreation(models.Model):
|
||||
self.ensure_one()
|
||||
if not self.ncr_id:
|
||||
raise UserError(_(
|
||||
'CAPA %s has no source NCR — verification activity '
|
||||
'CAPA %s has no source NCR - verification activity '
|
||||
'cannot be scheduled.'
|
||||
) % self.display_name)
|
||||
deadline = fields.Date.context_today(self) + timedelta(days=30)
|
||||
|
||||
@@ -9,14 +9,14 @@ from odoo import api, fields, models
|
||||
|
||||
|
||||
class FpQualityHold(models.Model):
|
||||
"""Quality Hold — parts pulled from production for quality review.
|
||||
"""Quality Hold - parts pulled from production for quality review.
|
||||
|
||||
Enables the Steelhead-style "Move Parts Into Quality Management"
|
||||
workflow. An operator can split a partial quantity off a job and
|
||||
place it on hold for inspection, rework, or scrap.
|
||||
"""
|
||||
_name = 'fusion.plating.quality.hold'
|
||||
_description = 'Fusion Plating — Quality Hold'
|
||||
_description = 'Fusion Plating - Quality Hold'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin', 'fp.parent.numbered.mixin']
|
||||
_order = 'create_date desc'
|
||||
|
||||
@@ -30,7 +30,7 @@ class FpQualityHold(models.Model):
|
||||
)
|
||||
|
||||
# ----- What's on hold -----
|
||||
# Phase 1 (Sub 11) — native plating-job link replaces the legacy
|
||||
# Phase 1 (Sub 11) - native plating-job link replaces the legacy
|
||||
# workorder_id / production_id pair that lived in bridge_mrp.
|
||||
# The bridge fields stay during the migration window so existing
|
||||
# records keep their FKs; Phase 5 removes bridge_mrp entirely.
|
||||
@@ -56,7 +56,7 @@ class FpQualityHold(models.Model):
|
||||
('contamination', 'Contamination'),
|
||||
('customer_complaint', 'Customer Complaint'),
|
||||
('process_deviation', 'Process Deviation'),
|
||||
# v19.0.4.8.0 — Distinct bucket so QA can split QC-failed
|
||||
# v19.0.4.8.0 - Distinct bucket so QA can split QC-failed
|
||||
# holds (auto-spawned by fusion.plating.quality.check.action_fail)
|
||||
# from operator-flagged process deviations / contamination.
|
||||
('qc_failure', 'QC Failure'),
|
||||
@@ -70,7 +70,7 @@ class FpQualityHold(models.Model):
|
||||
description = fields.Text(
|
||||
string='Description',
|
||||
required=True,
|
||||
help='Required — every hold needs an inspector narrative.',
|
||||
help='Required - every hold needs an inspector narrative.',
|
||||
)
|
||||
attachment_ids = fields.Many2many(
|
||||
'ir.attachment',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Sub 12 Phase C — trigger-based quality points.
|
||||
# Sub 12 Phase C - trigger-based quality points.
|
||||
#
|
||||
# Replaces the old "customer.x_fc_requires_qc + customer.x_fc_qc_template_id"
|
||||
# direct binding. Now an admin defines fp.quality.point rules with filters
|
||||
@@ -39,7 +39,7 @@ STEP_KINDS = [
|
||||
|
||||
class FpQualityPoint(models.Model):
|
||||
_name = 'fp.quality.point'
|
||||
_description = 'Fusion Plating — Quality Point'
|
||||
_description = 'Fusion Plating - Quality Point'
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'sequence, name'
|
||||
|
||||
@@ -179,7 +179,7 @@ class FpQualityPoint(models.Model):
|
||||
vals = {
|
||||
'template_id': self.template_id.id,
|
||||
}
|
||||
# Best-effort field bindings — survives schema variations.
|
||||
# Best-effort field bindings - survives schema variations.
|
||||
if 'partner_id' in Check._fields and partner:
|
||||
vals['partner_id'] = partner.id
|
||||
if 'job_id' in Check._fields and job:
|
||||
@@ -198,13 +198,13 @@ class FpQualityPoint(models.Model):
|
||||
return Check.create(vals)
|
||||
except Exception as e:
|
||||
_logger.warning(
|
||||
'fp.quality.point %s: spawn failed for %s — %s',
|
||||
'fp.quality.point %s: spawn failed for %s - %s',
|
||||
self.name, source.display_name if source else '?', e,
|
||||
)
|
||||
return False
|
||||
|
||||
def action_spawn_manual(self):
|
||||
"""Manual fire — present from the form view button. No source ctx."""
|
||||
"""Manual fire - present from the form view button. No source ctx."""
|
||||
for rec in self:
|
||||
rec._spawn_check_for(source=rec)
|
||||
return {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Sub 12 Phase C — trigger-hook overrides on receiving / job / step / SO.
|
||||
# Sub 12 Phase C - trigger-hook overrides on receiving / job / step / SO.
|
||||
# Each hook walks fp.quality.point with the matching trigger_type and
|
||||
# spawns a quality check for every match. Best-effort: failures are
|
||||
# logged but never block the underlying state transition.
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Sub 12 Phase B — quality reason (root-cause classification library).
|
||||
# Sub 12 Phase B - quality reason (root-cause classification library).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class FpQualityReason(models.Model):
|
||||
_name = 'fp.quality.reason'
|
||||
_description = 'Fusion Plating — Quality Reason'
|
||||
_description = 'Fusion Plating - Quality Reason'
|
||||
_order = 'category, name'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Sub 12 Phase D — smart-button counts on fp.job, sale.order, res.partner.
|
||||
# Sub 12 Phase D - smart-button counts on fp.job, sale.order, res.partner.
|
||||
#
|
||||
# Each parent record gets badge counts for: Holds, Checks, NCRs, CAPAs,
|
||||
# RMAs. The buttons hide at a count of 0 (set in the view XML), so the row
|
||||
# only shows quality work that actually exists. NCR/CAPA counts are scoped
|
||||
# to the order/job via RMAs + holds (see _fp_quality_ncr_ids) — NOT the
|
||||
# to the order/job via RMAs + holds (see _fp_quality_ncr_ids) - NOT the
|
||||
# customer's whole NCR history. Action methods open the matching list.
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
@@ -53,7 +53,7 @@ class FpJobQualitySmart(models.Model):
|
||||
[('ncr_id', 'in', ncr_ids)]) if ncr_ids else 0
|
||||
|
||||
def _fp_quality_ncr_ids(self):
|
||||
"""Job-scoped NCR ids — single source of truth for both the badge
|
||||
"""Job-scoped NCR ids - single source of truth for both the badge
|
||||
count and the smart-button list.
|
||||
|
||||
There is no ncr.job_id / ncr.sale_order_id field, so the only honest
|
||||
@@ -124,7 +124,7 @@ class FpJobQualitySmart(models.Model):
|
||||
def action_view_fp_rmas(self):
|
||||
self.ensure_one()
|
||||
# SO-scoped to match the badge count. RMA.sale_order_id is required,
|
||||
# so a job with no order matches nothing — same as the count's 0.
|
||||
# so a job with no order matches nothing - same as the count's 0.
|
||||
return {
|
||||
'name': _('RMAs'),
|
||||
'type': 'ir.actions.act_window',
|
||||
@@ -178,7 +178,7 @@ class SaleOrderQualitySmart(models.Model):
|
||||
[('ncr_id', 'in', ncr_ids)]) if ncr_ids else 0
|
||||
|
||||
def _fp_quality_ncr_ids(self):
|
||||
"""Order-scoped NCR ids — single source of truth for both the badge
|
||||
"""Order-scoped NCR ids - single source of truth for both the badge
|
||||
count and the smart-button list.
|
||||
|
||||
There is no ncr.sale_order_id / ncr.job_id field, so the only honest
|
||||
@@ -283,7 +283,7 @@ class ResPartnerQualitySmart(models.Model):
|
||||
def action_view_fp_quality_history(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': _('Quality History — %s') % self.display_name,
|
||||
'name': _('Quality History - %s') % self.display_name,
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'fp_quality_dashboard',
|
||||
'context': {'default_partner_id': self.id},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Sub 12 Phase B — quality tag.
|
||||
# Sub 12 Phase B - quality tag.
|
||||
#
|
||||
# Cross-cutting tag library reused by NCR, CAPA, Hold, Check, RMA.
|
||||
|
||||
@@ -12,7 +12,7 @@ from odoo import fields, models
|
||||
|
||||
class FpQualityTag(models.Model):
|
||||
_name = 'fp.quality.tag'
|
||||
_description = 'Fusion Plating — Quality Tag'
|
||||
_description = 'Fusion Plating - Quality Tag'
|
||||
_order = 'name'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Sub 12 Phase B — quality team.
|
||||
# Sub 12 Phase B - quality team.
|
||||
#
|
||||
# Dedicated team model rather than reusing res.groups, per Sub 12 locked
|
||||
# decision: teams need their own kanban grouping + per-team escalation
|
||||
@@ -14,7 +14,7 @@ from odoo import fields, models
|
||||
|
||||
class FpQualityTeam(models.Model):
|
||||
_name = 'fp.quality.team'
|
||||
_description = 'Fusion Plating — Quality Team'
|
||||
_description = 'Fusion Plating - Quality Team'
|
||||
_order = 'sequence, name'
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class FpQuoteConfigurator(models.Model):
|
||||
def _find_matching_rule(self):
|
||||
"""Extend the configurator's matcher to consider Spec + Recipe.
|
||||
|
||||
Spec match adds +8 (highest priority — explicit customer spec
|
||||
Spec match adds +8 (highest priority - explicit customer spec
|
||||
wins over chemistry filters). Recipe adds +6. Material is +2.
|
||||
"""
|
||||
recipe = self.recipe_id or False
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# fp.rma — Return Material Authorisation.
|
||||
# fp.rma - Return Material Authorisation.
|
||||
#
|
||||
# Sub 12 Phase A. Internal-only RMA workflow that ties customer returns to
|
||||
# the existing NCR / CAPA / Hold stack. Portal submission is deferred to a
|
||||
@@ -35,7 +35,7 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
class FpRma(models.Model):
|
||||
_name = 'fusion.plating.rma'
|
||||
_description = 'Fusion Plating — Return Material Authorisation'
|
||||
_description = 'Fusion Plating - Return Material Authorisation'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin', 'fp.parent.numbered.mixin']
|
||||
_order = 'create_date desc, id desc'
|
||||
_rec_name = 'name'
|
||||
@@ -204,7 +204,7 @@ class FpRma(models.Model):
|
||||
default=True, tracking=True,
|
||||
help='When the carrier delivers the returned parts and an '
|
||||
'fp.receiving is created against this RMA, an NCR is '
|
||||
'spawned automatically. Manager can toggle off — the '
|
||||
'spawned automatically. Manager can toggle off - the '
|
||||
'change is tracked on the chatter for audit.',
|
||||
)
|
||||
auto_spawn_hold = fields.Boolean(
|
||||
@@ -235,7 +235,7 @@ class FpRma(models.Model):
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Phase B placeholders (categorisation) — added now so views won't
|
||||
# Phase B placeholders (categorisation) - added now so views won't
|
||||
# break when Phase B lands. Kept as M2O/M2M to models added later.
|
||||
# ------------------------------------------------------------------
|
||||
# tag_ids, reason_id, team_id, stage_id are added in Phase B.
|
||||
@@ -370,7 +370,7 @@ class FpRma(models.Model):
|
||||
- from action_mark_received (manual)
|
||||
- from fp.receiving.create override when rma_id was set
|
||||
Flips state to `received` and (optionally) spawns NCR + Hold per
|
||||
the auto_spawn_* toggles. Idempotent — re-entry on an already-
|
||||
the auto_spawn_* toggles. Idempotent - re-entry on an already-
|
||||
received RMA is a no-op (no double-spawn on ORM retry / split
|
||||
deliveries).
|
||||
"""
|
||||
@@ -389,7 +389,7 @@ class FpRma(models.Model):
|
||||
spawned.append(_('Hold %s') % hold.name)
|
||||
label = 'Received'
|
||||
if spawned:
|
||||
label += ' — auto-spawned ' + ', '.join(spawned)
|
||||
label += ' - auto-spawned ' + ', '.join(spawned)
|
||||
rec._post_state_message(label)
|
||||
# Customer notification: parts arrived at the shop.
|
||||
rec._fire_rma_notification('rma_received')
|
||||
@@ -402,7 +402,7 @@ class FpRma(models.Model):
|
||||
if existing:
|
||||
return existing
|
||||
partner = self.partner_id
|
||||
# Pull a facility — prefer the partner's company facility, fall
|
||||
# Pull a facility - prefer the partner's company facility, fall
|
||||
# back to the first active facility.
|
||||
Facility = self.env['fusion.plating.facility']
|
||||
facility = (
|
||||
@@ -420,7 +420,7 @@ class FpRma(models.Model):
|
||||
) or self.sale_order_line_ids[:1].product_id.display_name or '/'
|
||||
complaint = self.complaint_description or ''
|
||||
body = (
|
||||
Markup('<p><strong>RMA %s — auto-created from customer return.</strong></p>') % self.name
|
||||
Markup('<p><strong>RMA %s - auto-created from customer return.</strong></p>') % self.name
|
||||
+ Markup(complaint or '<p>(no description)</p>')
|
||||
)
|
||||
ncr = Ncr.create({
|
||||
@@ -506,7 +506,7 @@ class FpRma(models.Model):
|
||||
'RMA %s must be Triaged or Resolving before being '
|
||||
'marked Resolved.'
|
||||
) % rec.display_name)
|
||||
# Refund path needs a wizard return — handle separately.
|
||||
# Refund path needs a wizard return - handle separately.
|
||||
refund_recs = self.filtered(lambda r: r.resolution_type == 'refund')
|
||||
if len(refund_recs) > 1:
|
||||
raise UserError(_(
|
||||
@@ -572,7 +572,7 @@ class FpRma(models.Model):
|
||||
new_job._generate_steps_from_recipe()
|
||||
self.replacement_job_id = new_job.id
|
||||
# Auto-confirm so the portal mirror, racking inspection and
|
||||
# 'job_confirmed' notification all fire — same as a normal job.
|
||||
# 'job_confirmed' notification all fire - same as a normal job.
|
||||
if hasattr(new_job, 'action_confirm') and new_job.state == 'draft':
|
||||
try:
|
||||
new_job.action_confirm()
|
||||
@@ -588,14 +588,14 @@ class FpRma(models.Model):
|
||||
if self.refund_invoice_id:
|
||||
return self.refund_invoice_id
|
||||
# Open the standard refund wizard pre-filled to the original SO.
|
||||
# We don't auto-confirm — accountant verifies amounts first.
|
||||
# We don't auto-confirm - accountant verifies amounts first.
|
||||
invoices = self.env['account.move'].search([
|
||||
('invoice_origin', '=', self.sale_order_id.name),
|
||||
('move_type', '=', 'out_invoice'),
|
||||
], limit=1)
|
||||
if not invoices:
|
||||
raise UserError(_(
|
||||
'RMA %s: no posted invoice found for SO %s — cannot '
|
||||
'RMA %s: no posted invoice found for SO %s - cannot '
|
||||
'create a credit note automatically. Issue refund '
|
||||
'manually.'
|
||||
) % (self.display_name, self.sale_order_id.name))
|
||||
|
||||
@@ -86,7 +86,7 @@ class FpReceivingRmaLink(models.Model):
|
||||
"""Walk an RMA-bound receiving from draft to closed in one call.
|
||||
|
||||
For RMA returns, the receiving's box-count → racking → close walk
|
||||
is purely administrative — the parts are already plated and the
|
||||
is purely administrative - the parts are already plated and the
|
||||
operator opens them on triage, not on intake. Fast-forwarding
|
||||
here keeps the SO's x_fc_receiving_status accurate without
|
||||
forcing the receiver to click three buttons in sequence.
|
||||
@@ -111,7 +111,7 @@ class AccountMoveRmaLink(models.Model):
|
||||
with resolution_type='refund' and no refund_invoice_id yet.
|
||||
|
||||
Also flips the RMA from `resolving` to `resolved` once the credit
|
||||
note is linked — mirrors the auto-progression for replace/rework
|
||||
note is linked - mirrors the auto-progression for replace/rework
|
||||
paths so the RMA doesn't get stuck after a refund.
|
||||
"""
|
||||
_inherit = 'account.move'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Phase 1 (Sub 11) — relocated from fusion_plating_bridge_mrp.
|
||||
# Phase 1 (Sub 11) - relocated from fusion_plating_bridge_mrp.
|
||||
# Adds the back-reference from fp.thickness.reading to the QC record
|
||||
# that produced it. Lives here (not in fusion_plating_certificates)
|
||||
# because the link target is fusion.plating.quality.check, owned by
|
||||
|
||||
@@ -14,10 +14,10 @@ class ResPartner(models.Model):
|
||||
help='When enabled, newly-created parts under this customer will '
|
||||
'show a reminder banner on the part form inviting QA to '
|
||||
'complete a Contract Review (QA-005). The review remains '
|
||||
'fully optional — the reminder can be dismissed and never '
|
||||
'fully optional - the reminder can be dismissed and never '
|
||||
'blocks production.',
|
||||
)
|
||||
# Phase 4 (Sub 11) — relocated from fusion_plating_bridge_mrp.
|
||||
# Phase 4 (Sub 11) - relocated from fusion_plating_bridge_mrp.
|
||||
x_fc_requires_qc = fields.Boolean(
|
||||
string='Require QC Sign-off',
|
||||
default=False, tracking=True,
|
||||
|
||||
Reference in New Issue
Block a user