@@ -0,0 +1,353 @@
# -*- coding: utf-8 -*-
""" Create a brand-new Hard Anodize Type III + Dye + Seal recipe with
rich operator instructions + measurement prompts on every step,
then create an SO for ABC Manufacturing and confirm it so the
operator-facing job is ready to run.
After running, the user navigates to:
- Recipe form (Process Recipes menu) — verify instructions present
- Simple Recipe Editor — verify per-step Instructions + Measurements
- Sale Orders — verify the new SO with line referencing the recipe
- Plating Jobs — verify job created with all steps
- Click Mark Done on any step → verify operator wizard shows
instructions + measurement prompts
"""
import json
from odoo import fields
Node = env [ ' fusion.plating.process.node ' ]
NodeInput = env [ ' fusion.plating.process.node.input ' ]
Template = env [ ' fp.step.template ' ]
print ( ' \n ========== Build Hard Anodize Type III Recipe ========== \n ' )
# Clean up any prior run of this script (prior recipes + their variants + jobs + SOs)
prior_recipes = Node . search ( [
' | ' , ( ' name ' , ' = ' , ' Hard Anodize Type III + Dye + Seal ' ) ,
( ' name ' , ' ilike ' , ' Hard Anodize Type III + Dye + Seal — ' ) ,
] )
if prior_recipes :
print ( ' Cleaning up %d prior recipe(s)... ' % len ( prior_recipes ) )
# Cancel + delete jobs that reference these recipes
prior_jobs = env [ ' fp.job ' ] . search ( [ ( ' recipe_id ' , ' in ' , prior_recipes . ids ) ] )
for j in prior_jobs :
try :
j . write ( { ' state ' : ' cancel ' } )
except Exception :
pass
prior_sos = env [ ' sale.order ' ] . search ( [
( ' order_line.x_fc_process_variant_id ' , ' in ' , prior_recipes . ids ) ,
( ' state ' , ' in ' , ( ' draft ' , ' sent ' ) ) ,
] )
prior_sos . unlink ( )
prior_jobs . unlink ( )
prior_recipes . unlink ( )
env . cr . commit ( )
# ----- Recipe root -----
recipe = Node . create ( {
' name ' : ' Hard Anodize Type III + Dye + Seal ' ,
' code ' : ' HARD_ANO_T3 ' ,
' node_type ' : ' recipe ' ,
' is_template ' : True ,
' description ' : ''' <p><strong>Hard Anodize Type III per MIL-A-8625F</strong></p>
<p>Aluminum substrate. Black sulfuric dye. Hot nickel acetate seal.</p>
<p>Tolerances: 0.0015 " – 0.0020" coating thickness, 60kV breakdown
voltage. Hardness ≥350HV300.</p> ''' ,
} )
print ( ' Created recipe: ' , recipe . id , recipe . name )
# ----- Step definitions -----
# Each spec: name, kind, description (operator instructions), extra_prompts (additional manually-authored)
STEPS = [
{
' name ' : ' 1. Receiving ' ,
' kind ' : ' receiving ' ,
' description ' : ''' <p><strong>Verify quantity and condition on arrival.</strong></p>
<ul><li>Count parts against the packing slip</li>
<li>Visually inspect for damage, corrosion, oil residue</li>
<li>Photograph any damage or non-conformance</li>
<li>Sign off below before parts move to staging</li></ul> ''' ,
} ,
{
' name ' : ' 2. Contract Review (QA-005) ' ,
' kind ' : ' contract_review ' ,
' description ' : ''' <p><strong>Verify the order against quality requirements.</strong></p>
<ul><li>Confirm spec matches PO (MIL-A-8625F Type III, Class 2 Black)</li>
<li>Cross-check thickness, hardness, salt-spray requirements</li>
<li>Open the QA-005 contract review form (separate dialog)</li>
<li>Approve only after all line items reconciled</li></ul> ''' ,
} ,
{
' name ' : ' 3. Incoming Inspection ' ,
' kind ' : ' inspect ' ,
' description ' : ''' <p><strong>First-piece inspection before processing.</strong></p>
<ul><li>Verify part number against drawing</li>
<li>Inspect surface for prior plating remnants, scratches, pitting</li>
<li>Photograph any defects</li>
<li>Record incoming dimensional spot-check</li></ul> ''' ,
} ,
{
' name ' : ' 4. Racking ' ,
' kind ' : ' racking ' ,
' description ' : ''' <p><strong>Mount parts on titanium rack.</strong></p>
<ul><li>Use Rack T-12 or similar (titanium, NOT aluminum)</li>
<li>Ensure firm electrical contact at rack hooks</li>
<li>Apply masking to threaded holes if required (see drawing)</li>
<li>Photograph the loaded rack before submerging</li></ul> ''' ,
} ,
{
' name ' : ' 5. Alkaline Clean (Tank A-1) ' ,
' kind ' : ' cleaning ' ,
' description ' : ''' <p><strong>Soak clean — Aluminum Etch Cleaner.</strong></p>
<ul><li>Tank: A-1, Bath: ALKCLEAN-1</li>
<li>Time: 4– 6 minutes</li>
<li>Temperature: 140– 160°F</li>
<li>Confirm titration done within last 24 hours</li></ul> ''' ,
} ,
{
' name ' : ' 6. Rinse — Cascade DI (Tank A-2) ' ,
' kind ' : ' rinse ' ,
' description ' : ''' <p><strong>Triple cascade DI rinse.</strong></p>
<ul><li>Tank A-2 (DI rinse, conductivity < 50 µS/cm)</li>
<li>Time: 30 seconds, agitate</li>
<li>Verify conductivity reading on bath log</li></ul> ''' ,
} ,
{
' name ' : ' 7. Etch (Tank A-3) ' ,
' kind ' : ' etch ' ,
' description ' : ''' <p><strong>Caustic etch — sodium hydroxide bath.</strong></p>
<ul><li>Tank: A-3, Bath: ETCH-1</li>
<li>Time: 30– 90 seconds (per drawing — heavy etch removes 0.0005 " /side)</li>
<li>Temperature: 130– 150°F</li>
<li>Concentration: 4– 6 oz/gal NaOH</li>
<li>HE-risk parts (high-strength) require post-bake — flag accordingly</li></ul> ''' ,
} ,
{
' name ' : ' 8. Rinse — Cascade DI (Tank A-4) ' ,
' kind ' : ' rinse ' ,
' description ' : ' <p>Triple cascade DI rinse — Tank A-4. 30 sec agitate.</p> ' ,
} ,
{
' name ' : ' 9. Desmut / Deoxidize (Tank A-5) ' ,
' kind ' : ' etch ' ,
' description ' : ''' <p><strong>Acid desmut to remove black smut from etch.</strong></p>
<ul><li>Tank: A-5, Bath: DEOX-1 (HNO3-based)</li>
<li>Time: 30– 60 seconds</li>
<li>Temperature: ambient</li>
<li>Surface should be water-break-free after this step</li></ul> ''' ,
} ,
{
' name ' : ' 10. Rinse — Cascade DI (Tank A-6) ' ,
' kind ' : ' rinse ' ,
' description ' : ' <p>Final pre-anodize rinse — Tank A-6. Conductivity must be < 50 µS/cm.</p> ' ,
} ,
{
' name ' : ' 11. Hard Anodize Type III (Tank A-9) ' ,
' kind ' : ' plate ' ,
' description ' : ''' <p><strong>HARD ANODIZE — the primary process step.</strong></p>
<ul><li>Tank: A-9, Bath: HARDANO-1 (15 % s ulfuric acid)</li>
<li>Temperature: 28– 32°F (chilled bath — confirm chiller is running)</li>
<li>Current density: 24– 36 ASF</li>
<li>Voltage ramp: 0– 80V over first 5 minutes</li>
<li>Time at voltage: 60 minutes (gives ~0.002 " coating)</li>
<li>Record amperage every 15 minutes</li>
<li>Check thickness midway with Fischerscope on witness coupon</li>
<li>If color reading off, halt and call supervisor</li></ul> ''' ,
} ,
{
' name ' : ' 12. Rinse — Cold (Tank A-12) ' ,
' kind ' : ' rinse ' ,
' description ' : ' <p>Cold cascade rinse to remove sulfuric residue. Tank A-12.</p> ' ,
} ,
{
' name ' : ' 13. Black Dye Bath (Tank A-14) ' ,
' kind ' : ' plate ' ,
' description ' : ''' <p><strong>Sulfo Black BL dye absorption.</strong></p>
<ul><li>Tank: A-14, Bath: DYE-BL-1</li>
<li>Temperature: 130– 150°F</li>
<li>Time: 12– 18 minutes</li>
<li>Maintain pH 5.0– 6.0</li>
<li>Visually verify uniform black with no streaks before sealing</li></ul> ''' ,
} ,
{
' name ' : ' 14. Rinse — Warm (Tank A-15) ' ,
' kind ' : ' rinse ' ,
' description ' : ' <p>Warm rinse before sealing. Tank A-15. ~110°F.</p> ' ,
} ,
{
' name ' : ' 15. Hot Nickel Acetate Seal (Tank A-16) ' ,
' kind ' : ' bake ' ,
' description ' : ''' <p><strong>Nickel acetate seal — locks in dye, improves corrosion resistance.</strong></p>
<ul><li>Tank: A-16, Bath: SEAL-NA-1</li>
<li>Temperature: 195– 205°F</li>
<li>Time: 18– 22 minutes</li>
<li>pH: 5.5– 6.0</li>
<li>Attach AMS-2759 chart-recorder file as photo before unloading</li>
<li>Quality of seal verified post-process by dye absorption test</li></ul> ''' ,
} ,
{
' name ' : ' 16. Hot DI Rinse (Tank A-17) ' ,
' kind ' : ' rinse ' ,
' description ' : ' <p>Final hot DI rinse. Tank A-17. 180°F+. Drives off residual seal solution.</p> ' ,
} ,
{
' name ' : ' 17. Drying (Hot Air Knife) ' ,
' kind ' : ' dry ' ,
' description ' : ''' <p><strong>Hot-air knife dry — leave parts on rack.</strong></p>
<ul><li>Hot air knife @ 180°F</li>
<li>Time: 5 minutes minimum</li>
<li>Verify parts fully dry before unracking — water spotting is a defect</li></ul> ''' ,
} ,
{
' name ' : ' 18. De-Racking ' ,
' kind ' : ' derack ' ,
' description ' : ''' <p><strong>Remove parts from rack carefully.</strong></p>
<ul><li>Wear lint-free gloves</li>
<li>Inspect each part for rack-mark touch-up needs</li>
<li>Place on padded staging cart</li></ul> ''' ,
} ,
{
' name ' : ' 19. Final Visual Inspection ' ,
' kind ' : ' final_inspect ' ,
' description ' : ''' <p><strong>Visual + dimensional + thickness QC.</strong></p>
<ul><li>Visual: uniform black, no streaks, scratches, or burn</li>
<li>Dimensional: spot-check 3 critical dims per part</li>
<li>Thickness: 5-point Fischerscope reading per drawing locations</li>
<li>Photograph any defects, route to NCR if rework needed</li>
<li>Inspector signs off below</li></ul> ''' ,
} ,
{
' name ' : ' 20. Microhardness Test (Witness Coupon) ' ,
' kind ' : ' hardness_test ' ,
' description ' : ''' <p><strong>HV300 hardness measurement on witness coupon.</strong></p>
<ul><li>Equipment: LECO LM247AT microhardness tester (verify cal date current)</li>
<li>Load: 300 gf, dwell 10s</li>
<li>Take 3 indents, log each + average</li>
<li>Spec: ≥350 HV300 (MIL-A-8625F Type III)</li></ul> ''' ,
} ,
{
' name ' : ' 21. Salt Spray (Witness, ASTM B117) ' ,
' kind ' : ' salt_spray ' ,
' description ' : ''' <p><strong>336-hour salt spray on witness coupon.</strong></p>
<ul><li>Submit 3 coupons to lab</li>
<li>Test duration: 336 hours minimum</li>
<li>Acceptance: zero red rust, < 5 % white corrosion</li>
<li>Attach lab certificate when received</li></ul> ''' ,
} ,
{
' name ' : ' 22. Packaging ' ,
' kind ' : ' packaging ' ,
' description ' : ''' <p><strong>Wrap and stage for shipment.</strong></p>
<ul><li>Wrap each part in VCI paper</li>
<li>Place in foam-lined box, max 4 parts per box</li>
<li>Include CoC + dimensional report inside the box</li>
<li>Seal with company tape</li></ul> ''' ,
} ,
{
' name ' : ' 23. Shipping ' ,
' kind ' : ' ship ' ,
' description ' : ''' <p><strong>Outbound — confirm carrier and BoL.</strong></p>
<ul><li>Carrier per SO (UPS / FedEx / customer pickup)</li>
<li>Print BoL, attach to package</li>
<li>Photograph sealed shipment for proof-of-shipment</li>
<li>Update tracking # in this SO</li></ul> ''' ,
} ,
]
# ----- Build steps -----
DEFAULTS = Template . DEFAULT_INPUTS_BY_KIND
total_prompts = 0
for idx , spec in enumerate ( STEPS ) :
step = Node . create ( {
' name ' : spec [ ' name ' ] ,
# node_type='operation' is required for fp.job to materialize a
# work-order step from this node. 'step' nodes are sub-elements
# under an operation (e.g. child rinses), not job-step builders.
' node_type ' : ' operation ' ,
' parent_id ' : recipe . id ,
' sequence ' : ( idx + 1 ) * 10 ,
' default_kind ' : spec [ ' kind ' ] ,
' description ' : spec [ ' description ' ] ,
' collect_measurements ' : True ,
} )
# Seed prompts based on kind
for input_spec in DEFAULTS . get ( spec [ ' kind ' ] , [ ] ) :
NodeInput . create ( {
' node_id ' : step . id ,
' name ' : input_spec [ ' name ' ] ,
' input_type ' : input_spec . get ( ' input_type ' , ' text ' ) ,
' kind ' : ' step_input ' ,
' collect ' : True ,
' sequence ' : input_spec . get ( ' sequence ' , 10 ) ,
' required ' : input_spec . get ( ' required ' , False ) ,
' hint ' : input_spec . get ( ' hint ' , ' ' ) ,
' selection_options ' : input_spec . get ( ' selection_options ' , ' ' ) ,
' target_unit ' : input_spec . get ( ' target_unit ' , False ) ,
} )
total_prompts + = 1
env . cr . commit ( )
print ( ' Built %d steps with %d total prompts ' % ( len ( STEPS ) , total_prompts ) )
# ----- Create SO for ABC Manufacturing -----
print ( ' \n ========== Create SO for ABC ========== \n ' )
abc = env [ ' res.partner ' ] . browse ( 943 )
part = env [ ' fp.part.catalog ' ] . browse ( 148 ) # ABC part 4321
prod = env [ ' product.product ' ] . search ( [ ] , limit = 1 )
so = env [ ' sale.order ' ] . create ( {
' partner_id ' : abc . id ,
' x_fc_planned_start_date ' : fields . Date . today ( ) ,
' x_fc_po_number ' : ' BT-PO-HARDANO-001 ' ,
' client_order_ref ' : ' BT-PO-HARDANO-001 ' ,
} )
sol = env [ ' sale.order.line ' ] . create ( {
' order_id ' : so . id ,
' product_id ' : prod . id ,
' product_uom_qty ' : 10 ,
' price_unit ' : 350.0 ,
' name ' : ' Hard Anodize Type III + Black Dye + Seal ' ,
' x_fc_part_catalog_id ' : part . id ,
' x_fc_process_variant_id ' : recipe . id ,
} )
print ( ' Created SO: ' , so . name , ' line ' , sol . id )
# Confirm — triggers fp.job creation
so . action_confirm ( )
print ( ' Confirmed SO. State = ' , so . state )
env . cr . commit ( )
# Find resulting job
job = env [ ' fp.job ' ] . search ( [ ( ' origin ' , ' = ' , so . name ) ] , limit = 1 )
print ( ' \n ========== Job created ========== \n ' )
print ( ' Job: ' , job . id , job . name , ' | recipe variant: ' , job . recipe_id . name if job . recipe_id else None )
job_steps = env [ ' fp.job.step ' ] . search ( [ ( ' job_id ' , ' = ' , job . id ) ] , order = ' sequence ' )
print ( ' Steps on job: ' , len ( job_steps ) )
print ( ' \n ========== Verification ========== \n ' )
prompts_visible = 0
instructions_visible = 0
for js in job_steps :
rn = js . recipe_node_id
if rn :
ins = bool ( rn . description )
prompts = len ( rn . input_ids . filtered ( lambda i : i . collect ) )
instructions_visible + = int ( ins )
prompts_visible + = prompts
print ( ' [ %2d ] %s — instructions= %s prompts= %d kind= %s ' % (
js . sequence , js . name , ' ✓ ' if ins else ' ✗ ' , prompts , rn . default_kind or ' - '
) )
print ( ' \n Summary: ' )
print ( ' Steps with instructions: ' , instructions_visible , ' / ' , len ( job_steps ) )
print ( ' Total prompts visible to operators: ' , prompts_visible )
print ( ' \n ========== URLs to verify ========== ' )
print ( ' Recipe: /odoo/action-fusion_plating.action_fp_process_node/ %d ' % recipe . id )
print ( ' Sale Order:/odoo/sales/ %d ' % so . id )
print ( ' Job: /odoo/action-fusion_plating_jobs.action_fp_job/ %d ' % job . id )
print ( ' → click any step → " Mark Done " → operator wizard should show: ' )
print ( ' - Step description (instructions for operator) ' )
print ( ' - All %d collect=True prompts as input fields ' % prompts_visible )