diff --git a/odoo/scripts/generate_pro_appliance_demo.py b/odoo/scripts/generate_pro_appliance_demo.py new file mode 100644 index 0000000..de55810 --- /dev/null +++ b/odoo/scripts/generate_pro_appliance_demo.py @@ -0,0 +1,319 @@ +# Run with: +# odoo shell -c /etc/odoo/odoo.conf -d < odoo/scripts/generate_pro_appliance_demo.py + +from datetime import timedelta + +from odoo import fields + + +SEED_TAG = "[SEED:pro-appliance.repair]" +SOURCE_URL = "https://pro-appliance.repair/" + + +CONTACTS = [ + {"name": "Christina Gomez", "phone": "+1-302-503-0047"}, + {"name": "Clinton Parker", "phone": "+1-302-503-0048"}, + {"name": "Mike Kreller", "phone": "+1-302-503-0049"}, + {"name": "Frank Smith", "phone": "+1-302-503-0050"}, + {"name": "Mandy Cekine", "phone": "+1-302-503-0051"}, +] + +LOCATIONS = [ + { + "description": "Newark Downtown, DE 19713", + "latitude": 39.683723, + "longitude": -75.749657, + }, + { + "description": "Christiana Area, Newark, DE", + "latitude": 39.678002, + "longitude": -75.659408, + }, + { + "description": "Bear Area, New Castle County, DE", + "latitude": 39.629276, + "longitude": -75.658262, + }, +] + +WORK_TYPES = [ + ("Clothes Dryer", 90), + ("Dishwasher", 120), + ("Range", 120), + ("Refrigerator", 120), + ("Microwave", 90), +] + +FALLBACK_MATERIALS = [ + ("Dryer Belt", "DRY-BELT", 35.0), + ("Dishwasher Drain Pump", "DW-PUMP", 72.0), + ("Range Igniter", "RANGE-IGN", 48.0), + ("Refrigerator Thermistor", "FRIDGE-THRM", 29.0), + ("Microwave Door Switch", "MW-SWITCH", 17.0), +] + + +def ensure_source(): + source_model = env["contact.source"].sudo() + source = source_model.search([("name", "=", "pro-appliance.repair Website")], limit=1) + if not source: + source = source_model.create( + { + "name": "pro-appliance.repair Website", + "description": f"Generated from {SOURCE_URL} {SEED_TAG}", + "state": "active", + "active": True, + } + ) + return source + + +def ensure_phone_type(): + phone_type = env["dsrpt.communication.type"].sudo().search([("code", "=", "phone")], limit=1) + return phone_type + + +def ensure_work_types(): + model = env["repair.work.type"].sudo() + result = [] + for name, duration in WORK_TYPES: + rec = model.search([("name", "=", name)], limit=1) + if not rec: + rec = model.create({"name": name, "duration_min": duration, "active": True}) + else: + values = {} + if not rec.duration_min: + values["duration_min"] = duration + if not rec.active: + values["active"] = True + if values: + rec.write(values) + result.append(rec) + return result + + +def ensure_materials(): + material_model = env["repair.material"].sudo() + materials = material_model.search([("state", "=", "active")], order="id", limit=20) + if materials: + return materials + + existing_any = material_model.search([], order="id", limit=20) + if existing_any: + for rec in existing_any.filtered(lambda m: m.state != "active"): + rec.action_set_active() + return material_model.search([("state", "=", "active")], order="id", limit=20) + + created = material_model.browse() + for name, code, cost in FALLBACK_MATERIALS: + rec = material_model.create( + { + "name": name, + "default_code": code, + "uom_name": "pcs", + "standard_cost": cost, + "state": "active", + "active": True, + } + ) + created |= rec + return created + + +def ensure_technician(work_types): + technician_model = env["repair.technician"].sudo() + zone_model = env["repair.fsm.zone"].sudo() + + technician = technician_model.search([("state", "=", "active")], limit=1, order="id") + if technician: + missing_types = work_types - technician.work_type_ids + if missing_types: + technician.write({"work_type_ids": [(4, wt.id) for wt in missing_types]}) + return technician + + zones = zone_model.search([("state", "=", "active")], limit=3) + technician = technician_model.create( + { + "name": "Alex Pro Appliance", + "state": "active", + "active": True, + "work_type_ids": [(6, 0, work_types.ids)], + "zone_ids": [(6, 0, zones.ids)], + } + ) + + schedule_model = env["repair.technician.schedule"].sudo() + for day in ["0", "1", "2", "3", "4", "5"]: + schedule_model.create( + { + "technician_id": technician.id, + "day_of_week": day, + "hour_from": 9.0, + "hour_to": 17.0, + } + ) + return technician + + +def ensure_contact_and_address(payload, location, source, phone_type): + contact_model = env["dsrpt.contact"].sudo() + address_model = env["dsrpt.contact.address"].sudo() + comm_model = env["dsrpt.contact.communication"].sudo() + + contact = contact_model.search([("name", "=", payload["name"])], limit=1) + if not contact: + contact = contact_model.create( + { + "name": payload["name"], + "status": "has_request", + "source_id": source.id, + "note": f"Imported from {SOURCE_URL} {SEED_TAG}", + } + ) + + address = address_model.search( + [ + ("contact_id", "=", contact.id), + ("description", "=", location["description"]), + ], + limit=1, + ) + if not address: + address = address_model.create( + { + "contact_id": contact.id, + "description": location["description"], + "latitude": location["latitude"], + "longitude": location["longitude"], + } + ) + + if phone_type: + existing_phone = comm_model.search( + [ + ("contact_id", "=", contact.id), + ("communication_type_id", "=", phone_type.id), + ("value", "=", payload["phone"]), + ], + limit=1, + ) + if not existing_phone: + comm_model.create( + { + "contact_id": contact.id, + "communication_type_id": phone_type.id, + "value": payload["phone"], + "is_preferred": True if not contact.communication_ids else False, + } + ) + + return contact, address + + +def ensure_work_order(contact, address, work_type, technician, materials, index): + order_model = env["repair.work.order"].sudo() + time_model = env["repair.work.order.time"].sudo() + mat_line_model = env["repair.work.order.material"].sudo() + + desc = f"{SEED_TAG} {work_type.name} service request from {SOURCE_URL}" + existing = order_model.search( + [ + ("contact_id", "=", contact.id), + ("work_type_id", "=", work_type.id), + ("description", "=", desc), + ], + limit=1, + ) + if existing: + return existing, False + + start_dt = fields.Datetime.now() + timedelta(days=index + 1, hours=index) + duration_min = max(work_type.duration_min or 90, 30) + end_dt = start_dt + timedelta(minutes=duration_min) + + order = order_model.create( + { + "contact_id": contact.id, + "contact_address_id": address.id, + "work_type_id": work_type.id, + "description": desc, + "requested_datetime": fields.Datetime.now(), + "scheduled_datetime": start_dt, + "scheduled_end": end_dt, + "slot_day": fields.Date.to_date(start_dt), + "technician_id": technician.id if technician else False, + "state": "assigned" if technician else "confirmed", + } + ) + + time_model.create( + { + "work_order_id": order.id, + "technician_id": technician.id if technician else False, + "description": f"{work_type.name} diagnostics and repair", + "hours": round(duration_min / 60.0, 2), + } + ) + + if materials: + mat1 = materials[index % len(materials)] + mat_line_model.create( + { + "work_order_id": order.id, + "material_id": mat1.id, + "qty": 1.0 + (index % 2), + } + ) + if len(materials) > 1: + mat2 = materials[(index + 1) % len(materials)] + mat_line_model.create( + { + "work_order_id": order.id, + "material_id": mat2.id, + "qty": 1.0, + } + ) + + return order, True + + +def run(): + source = ensure_source() + phone_type = ensure_phone_type() + work_types = ensure_work_types() + materials = ensure_materials() + technician = ensure_technician(env["repair.work.type"].sudo().browse([wt.id for wt in work_types])) + + created_contacts = 0 + created_addresses = 0 + created_orders = 0 + + for idx, payload in enumerate(CONTACTS): + location = LOCATIONS[idx % len(LOCATIONS)] + contact_before = env["dsrpt.contact"].sudo().search_count([("name", "=", payload["name"])]) + address_before = env["dsrpt.contact.address"].sudo().search_count( + [("description", "=", location["description"])] + ) + + contact, address = ensure_contact_and_address(payload, location, source, phone_type) + if contact_before == 0: + created_contacts += 1 + if address_before == 0: + created_addresses += 1 + + work_type = work_types[idx % len(work_types)] + _order, was_created = ensure_work_order(contact, address, work_type, technician, materials, idx) + if was_created: + created_orders += 1 + + env.cr.commit() + + print("=== Pro Appliance demo seed completed ===") + print(f"Source: {SOURCE_URL}") + print(f"Contacts created: {created_contacts}") + print(f"Addresses created: {created_addresses}") + print(f"Work orders created: {created_orders}") + print(f"Materials available for linking: {len(materials)}") + + +run()