Files
MokoSuiteField/source/packages/plg_system_mokosuitefield/sql/install.mysql.sql
T
Jonathan Miller b05234ef1a feat: initial scaffold — MokoSuiteField service management
Layer 2 add-on for MokoSuite CRM. Field service operations for
plumbing, electrical, HVAC, and general trades.

Schema (12 tables):
- Technicians (trade, certifications, GPS, service radius, vehicle)
- Service Locations (customer properties with access notes, GPS)
- Work Orders (full lifecycle: new > dispatched > en_route > on_site >
  in_progress > completed > invoiced, with priority/trade/category)
- WO Line Items (labor, parts, materials, flat rate, permits)
- WO Photos (before/during/after with GPS coordinates)
- Service Agreements (recurring maintenance contracts with SLA)
- Equipment (HVAC units, panels, water heaters with serial/warranty)
- Vehicles (fleet tracking with mileage, inspection, GPS)
- Truck Stock (per-vehicle parts inventory with reorder points)
- Dispatch Log (assignment and routing history with GPS)
- Estimates (on-site quoting with token-based customer acceptance)
- Time Entries (per-WO labor tracking with overtime/travel flags)

Helpers:
- DispatchHelper: find best tech by trade/location/workload, dispatch
  board, auto-assignment, unassigned queue
- WorkOrderHelper: create, status lifecycle, completion with signature,
  dashboard stats

Infrastructure:
- Joomla 6 (PHP 8.3+), admin dashboard with dispatch board
- Git submodules: MokoSuite + MokoSuiteCRM
- GPL-3.0 license
2026-06-13 06:23:59 -05:00

333 lines
16 KiB
SQL

--
-- MokoSuite Field Service Tables
--
-- ============================================================
-- Technicians — extends CRM contacts / HRM employees
-- ============================================================
CREATE TABLE IF NOT EXISTS `#__mokosuitefield_technicians` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`contact_id` INT NOT NULL COMMENT 'FK to #__contact_details',
`employee_id` INT UNSIGNED DEFAULT NULL COMMENT 'FK to HRM employees if installed',
`tech_number` VARCHAR(20) NOT NULL DEFAULT '',
`status` ENUM('available','dispatched','en_route','on_site','off_duty','on_leave') NOT NULL DEFAULT 'available',
`trade` ENUM('general','plumbing','electrical','hvac','appliance','carpentry','painting','roofing','landscaping','pest_control','locksmith','multi_trade') NOT NULL DEFAULT 'general',
`license_number` VARCHAR(100) DEFAULT NULL COMMENT 'Trade license (e.g., master plumber)',
`license_expiry` DATE DEFAULT NULL,
`certifications` JSON DEFAULT NULL COMMENT '["EPA 608","backflow certified","journeyman electrician"]',
`skills` JSON DEFAULT NULL,
`hourly_rate` DECIMAL(10,2) DEFAULT NULL,
`overtime_rate` DECIMAL(10,2) DEFAULT NULL,
`max_daily_jobs` INT UNSIGNED NOT NULL DEFAULT 8,
`service_radius_miles` INT UNSIGNED NOT NULL DEFAULT 30,
`home_zip` VARCHAR(10) DEFAULT NULL,
`home_lat` DECIMAL(10,7) DEFAULT NULL,
`home_lng` DECIMAL(10,7) DEFAULT NULL,
`current_lat` DECIMAL(10,7) DEFAULT NULL,
`current_lng` DECIMAL(10,7) DEFAULT NULL,
`last_location_update` DATETIME DEFAULT NULL,
`vehicle_id` INT UNSIGNED DEFAULT NULL,
`notes` TEXT,
`created` DATETIME NOT NULL,
`modified` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_contact` (`contact_id`),
KEY `idx_status` (`status`),
KEY `idx_trade` (`trade`),
KEY `idx_vehicle` (`vehicle_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
-- Service Locations — customer property/site records
-- ============================================================
CREATE TABLE IF NOT EXISTS `#__mokosuitefield_locations` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`contact_id` INT NOT NULL COMMENT 'FK to CRM contact (property owner)',
`name` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'e.g., "Main Office", "123 Oak St Residence"',
`address` TEXT NOT NULL,
`city` VARCHAR(100) NOT NULL DEFAULT '',
`state` VARCHAR(50) NOT NULL DEFAULT '',
`zip` VARCHAR(20) NOT NULL DEFAULT '',
`latitude` DECIMAL(10,7) DEFAULT NULL,
`longitude` DECIMAL(10,7) DEFAULT NULL,
`property_type` ENUM('residential','commercial','industrial','multi_family','government','other') NOT NULL DEFAULT 'residential',
`access_notes` TEXT COMMENT 'Gate codes, parking, pet warnings, key location',
`equipment_notes` TEXT COMMENT 'Known equipment at this location',
`service_history_count` INT UNSIGNED NOT NULL DEFAULT 0,
`last_service_date` DATE DEFAULT NULL,
`created` DATETIME NOT NULL,
`modified` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_contact` (`contact_id`),
KEY `idx_zip` (`zip`),
KEY `idx_type` (`property_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
-- Work Orders — the core job record
-- ============================================================
CREATE TABLE IF NOT EXISTS `#__mokosuitefield_work_orders` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`wo_number` VARCHAR(30) NOT NULL DEFAULT '',
`contact_id` INT NOT NULL COMMENT 'Customer',
`location_id` INT UNSIGNED DEFAULT NULL,
`technician_id` INT UNSIGNED DEFAULT NULL,
`trade` ENUM('general','plumbing','electrical','hvac','appliance','carpentry','painting','roofing','landscaping','pest_control','locksmith','multi_trade') NOT NULL DEFAULT 'general',
`priority` ENUM('emergency','urgent','high','normal','low','scheduled') NOT NULL DEFAULT 'normal',
`status` ENUM('new','dispatched','en_route','on_site','in_progress','parts_needed','on_hold','completed','invoiced','cancelled') NOT NULL DEFAULT 'new',
`category` VARCHAR(100) DEFAULT NULL COMMENT 'e.g., "water heater", "panel upgrade", "AC repair"',
`description` TEXT NOT NULL,
`customer_po` VARCHAR(100) DEFAULT NULL COMMENT 'Customer PO number',
`scheduled_date` DATE DEFAULT NULL,
`scheduled_time_start` TIME DEFAULT NULL,
`scheduled_time_end` TIME DEFAULT NULL,
`actual_arrival` DATETIME DEFAULT NULL,
`actual_departure` DATETIME DEFAULT NULL,
`work_performed` TEXT,
`diagnosis` TEXT,
`parts_total` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`labor_total` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`tax` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`total` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`payment_status` ENUM('unpaid','partial','paid','waived') NOT NULL DEFAULT 'unpaid',
`payment_method` VARCHAR(50) DEFAULT NULL,
`customer_signature` TEXT COMMENT 'Base64 signature data',
`customer_signed_at` DATETIME DEFAULT NULL,
`service_agreement_id` INT UNSIGNED DEFAULT NULL,
`invoice_id` INT UNSIGNED DEFAULT NULL COMMENT 'FK to CRM invoices',
`deal_id` INT UNSIGNED DEFAULT NULL COMMENT 'FK to CRM deals',
`source` ENUM('phone','website','walk_in','referral','service_agreement','emergency','recurring') NOT NULL DEFAULT 'phone',
`created_by` INT NOT NULL DEFAULT 0,
`created` DATETIME NOT NULL,
`modified` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_wo_number` (`wo_number`),
KEY `idx_contact` (`contact_id`),
KEY `idx_location` (`location_id`),
KEY `idx_tech` (`technician_id`),
KEY `idx_status` (`status`),
KEY `idx_priority` (`priority`),
KEY `idx_trade` (`trade`),
KEY `idx_scheduled` (`scheduled_date`),
KEY `idx_agreement` (`service_agreement_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
-- Work Order Line Items — parts and labor
-- ============================================================
CREATE TABLE IF NOT EXISTS `#__mokosuitefield_wo_items` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`work_order_id` INT UNSIGNED NOT NULL,
`item_type` ENUM('labor','part','material','flat_rate','discount','permit','disposal') NOT NULL DEFAULT 'labor',
`product_id` INT UNSIGNED DEFAULT NULL COMMENT 'FK to CRM products for parts',
`description` VARCHAR(500) NOT NULL,
`quantity` DECIMAL(10,2) NOT NULL DEFAULT 1.00,
`unit_price` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`line_total` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`taxable` TINYINT NOT NULL DEFAULT 1,
`from_truck_stock` TINYINT NOT NULL DEFAULT 0 COMMENT 'Taken from tech truck inventory',
`sort_order` INT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_wo` (`work_order_id`),
KEY `idx_type` (`item_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
-- Work Order Photos — before/after, damage, equipment
-- ============================================================
CREATE TABLE IF NOT EXISTS `#__mokosuitefield_wo_photos` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`work_order_id` INT UNSIGNED NOT NULL,
`file_path` VARCHAR(500) NOT NULL,
`thumbnail_path` VARCHAR(500) DEFAULT NULL,
`photo_type` ENUM('before','during','after','damage','equipment','permit','other') NOT NULL DEFAULT 'other',
`caption` VARCHAR(500) DEFAULT NULL,
`latitude` DECIMAL(10,7) DEFAULT NULL,
`longitude` DECIMAL(10,7) DEFAULT NULL,
`taken_at` DATETIME DEFAULT NULL,
`uploaded_by` INT NOT NULL DEFAULT 0,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_wo` (`work_order_id`),
KEY `idx_type` (`photo_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
-- Service Agreements — recurring maintenance contracts
-- ============================================================
CREATE TABLE IF NOT EXISTS `#__mokosuitefield_agreements` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`contact_id` INT NOT NULL,
`location_id` INT UNSIGNED DEFAULT NULL,
`agreement_number` VARCHAR(30) NOT NULL DEFAULT '',
`title` VARCHAR(255) NOT NULL,
`trade` ENUM('general','plumbing','electrical','hvac','appliance','multi_trade') NOT NULL DEFAULT 'general',
`agreement_type` ENUM('preventive','full_service','parts_only','labor_only','priority_response') NOT NULL DEFAULT 'preventive',
`visits_per_year` INT UNSIGNED NOT NULL DEFAULT 2,
`visits_used` INT UNSIGNED NOT NULL DEFAULT 0,
`annual_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`billing_frequency` ENUM('monthly','quarterly','semi_annual','annual') NOT NULL DEFAULT 'annual',
`start_date` DATE NOT NULL,
`end_date` DATE DEFAULT NULL,
`auto_renew` TINYINT NOT NULL DEFAULT 1,
`sla_response_hours` INT UNSIGNED DEFAULT NULL COMMENT 'Guaranteed response time',
`parts_discount_pct` DECIMAL(5,2) NOT NULL DEFAULT 0.00,
`labor_discount_pct` DECIMAL(5,2) NOT NULL DEFAULT 0.00,
`status` ENUM('active','expired','cancelled','pending_renewal') NOT NULL DEFAULT 'active',
`equipment_covered` JSON DEFAULT NULL COMMENT 'List of equipment IDs covered',
`notes` TEXT,
`created` DATETIME NOT NULL,
`modified` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_number` (`agreement_number`),
KEY `idx_contact` (`contact_id`),
KEY `idx_location` (`location_id`),
KEY `idx_status` (`status`),
KEY `idx_end` (`end_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
-- Equipment — customer equipment tracked for service history
-- ============================================================
CREATE TABLE IF NOT EXISTS `#__mokosuitefield_equipment` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`location_id` INT UNSIGNED NOT NULL,
`contact_id` INT NOT NULL,
`equipment_type` ENUM('water_heater','furnace','ac_unit','heat_pump','boiler','electrical_panel','generator','sump_pump','water_softener','tankless_heater','mini_split','rooftop_unit','compressor','chiller','other') NOT NULL DEFAULT 'other',
`make` VARCHAR(100) DEFAULT NULL,
`model` VARCHAR(100) DEFAULT NULL,
`serial_number` VARCHAR(100) DEFAULT NULL,
`install_date` DATE DEFAULT NULL,
`warranty_expiry` DATE DEFAULT NULL,
`last_service_date` DATE DEFAULT NULL,
`next_service_date` DATE DEFAULT NULL,
`condition` ENUM('excellent','good','fair','poor','needs_replacement') DEFAULT NULL,
`location_detail` VARCHAR(255) DEFAULT NULL COMMENT 'e.g., "basement", "roof", "garage"',
`refrigerant_type` VARCHAR(20) DEFAULT NULL COMMENT 'HVAC: R-410A, R-22, etc.',
`capacity` VARCHAR(50) DEFAULT NULL COMMENT 'e.g., "50 gal", "3 ton", "200 amp"',
`fuel_type` VARCHAR(20) DEFAULT NULL COMMENT 'gas, electric, oil, propane',
`notes` TEXT,
`photo_path` VARCHAR(500) DEFAULT NULL,
`qr_code` VARCHAR(100) DEFAULT NULL COMMENT 'QR code on equipment sticker for quick lookup',
`created` DATETIME NOT NULL,
`modified` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_location` (`location_id`),
KEY `idx_contact` (`contact_id`),
KEY `idx_type` (`equipment_type`),
KEY `idx_serial` (`serial_number`),
KEY `idx_qr` (`qr_code`),
KEY `idx_next_service` (`next_service_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
-- Vehicles — service fleet tracking
-- ============================================================
CREATE TABLE IF NOT EXISTS `#__mokosuitefield_vehicles` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`vehicle_number` VARCHAR(20) NOT NULL,
`make` VARCHAR(50) DEFAULT NULL,
`model` VARCHAR(50) DEFAULT NULL,
`year` INT UNSIGNED DEFAULT NULL,
`vin` VARCHAR(17) DEFAULT NULL,
`license_plate` VARCHAR(20) DEFAULT NULL,
`assigned_tech_id` INT UNSIGNED DEFAULT NULL,
`status` ENUM('active','maintenance','retired') NOT NULL DEFAULT 'active',
`mileage` INT UNSIGNED DEFAULT NULL,
`last_inspection` DATE DEFAULT NULL,
`next_inspection` DATE DEFAULT NULL,
`insurance_expiry` DATE DEFAULT NULL,
`gps_device_id` VARCHAR(100) DEFAULT NULL,
`notes` TEXT,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_tech` (`assigned_tech_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
-- Truck Stock — parts inventory per vehicle
-- ============================================================
CREATE TABLE IF NOT EXISTS `#__mokosuitefield_truck_stock` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`vehicle_id` INT UNSIGNED NOT NULL,
`product_id` INT UNSIGNED NOT NULL COMMENT 'FK to CRM products',
`quantity` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`min_quantity` DECIMAL(10,2) NOT NULL DEFAULT 1.00 COMMENT 'Reorder point',
`last_restocked` DATE DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_vehicle_product` (`vehicle_id`, `product_id`),
KEY `idx_vehicle` (`vehicle_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
-- Dispatch Log — assignment and routing history
-- ============================================================
CREATE TABLE IF NOT EXISTS `#__mokosuitefield_dispatch_log` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`work_order_id` INT UNSIGNED NOT NULL,
`technician_id` INT UNSIGNED NOT NULL,
`action` ENUM('assigned','accepted','rejected','en_route','arrived','completed','reassigned','cancelled') NOT NULL,
`notes` TEXT,
`latitude` DECIMAL(10,7) DEFAULT NULL,
`longitude` DECIMAL(10,7) DEFAULT NULL,
`created_by` INT NOT NULL DEFAULT 0,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_wo` (`work_order_id`),
KEY `idx_tech` (`technician_id`),
KEY `idx_action` (`action`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
-- Estimates — on-site quoting
-- ============================================================
CREATE TABLE IF NOT EXISTS `#__mokosuitefield_estimates` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`work_order_id` INT UNSIGNED DEFAULT NULL COMMENT 'Generated from a WO inspection',
`contact_id` INT NOT NULL,
`location_id` INT UNSIGNED DEFAULT NULL,
`estimate_number` VARCHAR(30) NOT NULL DEFAULT '',
`title` VARCHAR(255) NOT NULL,
`description` TEXT,
`parts_total` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`labor_total` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`tax` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`total` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`status` ENUM('draft','sent','viewed','accepted','declined','expired','converted') NOT NULL DEFAULT 'draft',
`valid_days` INT UNSIGNED NOT NULL DEFAULT 30,
`token` VARCHAR(64) DEFAULT NULL COMMENT 'Public view/accept token',
`accepted_at` DATETIME DEFAULT NULL,
`customer_signature` TEXT,
`converted_wo_id` INT UNSIGNED DEFAULT NULL COMMENT 'WO created from accepted estimate',
`created_by` INT NOT NULL DEFAULT 0,
`created` DATETIME NOT NULL,
`modified` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_number` (`estimate_number`),
KEY `idx_contact` (`contact_id`),
KEY `idx_wo` (`work_order_id`),
KEY `idx_status` (`status`),
KEY `idx_token` (`token`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
-- Time Entries — tech labor tracking per work order
-- ============================================================
CREATE TABLE IF NOT EXISTS `#__mokosuitefield_time_entries` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`work_order_id` INT UNSIGNED NOT NULL,
`technician_id` INT UNSIGNED NOT NULL,
`start_time` DATETIME NOT NULL,
`end_time` DATETIME DEFAULT NULL,
`hours` DECIMAL(5,2) DEFAULT NULL,
`is_overtime` TINYINT NOT NULL DEFAULT 0,
`is_travel` TINYINT NOT NULL DEFAULT 0,
`rate` DECIMAL(10,2) DEFAULT NULL,
`notes` TEXT,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_wo` (`work_order_id`),
KEY `idx_tech` (`technician_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;