diff --git a/includes/Models/AgentCapacity.php b/includes/Models/AgentCapacity.php new file mode 100644 index 0000000..370a888 --- /dev/null +++ b/includes/Models/AgentCapacity.php @@ -0,0 +1,193 @@ +get_row( + $wpdb->prepare( + "SELECT * FROM {$table} WHERE user_id = %d AND channel = %s", + $user_id, + $channel + ) + ); + } + + /** + * Find the capacity row for a user/channel, creating it with defaults + * (ceiling 10, count 0) if it does not yet exist. + * + * @param int|string $user_id + * @param string $channel + * @return object|null + */ + public static function find_or_create($user_id, $channel = 'default') + { + $existing = static::for_user($user_id, $channel); + if ($existing) { + return $existing; + } + + static::create([ + 'user_id' => $user_id, + 'channel' => $channel, + 'max_concurrent' => self::DEFAULT_MAX_CONCURRENT, + 'current_count' => 0, + ]); + + return static::for_user($user_id, $channel); + } + + /** + * Create a new capacity row. + * + * @return int|false Inserted ID or false on failure. + */ + public static function create(array $data) + { + global $wpdb; + $table = static::table(); + $now = current_time('mysql'); + + $data['created_at'] = $now; + $data['updated_at'] = $now; + + $result = $wpdb->insert($table, $data); + + return $result !== false ? $wpdb->insert_id : false; + } + + /** + * Update a capacity row. + * + * @param int $id + * @return bool + */ + public static function update($id, array $data) + { + global $wpdb; + $table = static::table(); + + $data['updated_at'] = current_time('mysql'); + + return $wpdb->update($table, $data, ['id' => $id]) !== false; + } + + /** + * Increment the running load for a user/channel. + * + * @param int|string $user_id + * @param string $channel + * @return void + */ + public static function increment($user_id, $channel = 'default') + { + $row = static::find_or_create($user_id, $channel); + if ($row) { + static::update($row->id, ['current_count' => (int) $row->current_count + 1]); + } + } + + /** + * Decrement the running load for a user/channel (never below zero). + * + * @param int|string $user_id + * @param string $channel + * @return void + */ + public static function decrement($user_id, $channel = 'default') + { + $row = static::find_or_create($user_id, $channel); + if ($row && (int) $row->current_count > 0) { + static::update($row->id, ['current_count' => (int) $row->current_count - 1]); + } + } + + /** + * Get all capacity rows, ordered by agent then channel. + * + * @return array + */ + public static function all() + { + global $wpdb; + $table = static::table(); + + return $wpdb->get_results( + "SELECT * FROM {$table} ORDER BY user_id ASC, channel ASC" + ) ?: []; + } +} diff --git a/includes/Services/CapacityService.php b/includes/Services/CapacityService.php new file mode 100644 index 0000000..da8c78f --- /dev/null +++ b/includes/Services/CapacityService.php @@ -0,0 +1,66 @@ +current_count, $row->max_concurrent); + } + + /** + * Increment the agent's running load. + * + * @param int|string $user_id + * @param string $channel + * @return void + */ + public function increment_load($user_id, $channel = 'default') + { + AgentCapacity::increment($user_id, $channel); + } + + /** + * Decrement the agent's running load (never below zero). + * + * @param int|string $user_id + * @param string $channel + * @return void + */ + public function decrement_load($user_id, $channel = 'default') + { + AgentCapacity::decrement($user_id, $channel); + } + + /** + * All capacity rows for the admin view. + * + * @return array + */ + public function all_capacities() + { + return AgentCapacity::all(); + } +} diff --git a/includes/class-activator.php b/includes/class-activator.php index 8bb97e5..70188c9 100644 --- a/includes/class-activator.php +++ b/includes/class-activator.php @@ -585,6 +585,20 @@ private static function create_tables(): void KEY subject_lookup (subject_type, subject_id) ) $charset_collate;"; dbDelta($sql); + + // 31. escalated_agent_capacity + $sql = "CREATE TABLE {$prefix}agent_capacity ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + user_id BIGINT UNSIGNED NOT NULL, + channel VARCHAR(64) NOT NULL DEFAULT 'default', + max_concurrent INT UNSIGNED NOT NULL DEFAULT 10, + current_count INT UNSIGNED NOT NULL DEFAULT 0, + created_at DATETIME, + updated_at DATETIME, + PRIMARY KEY (id), + UNIQUE KEY user_channel (user_id, channel) + ) $charset_collate;"; + dbDelta($sql); } /** diff --git a/tests/Test_Capacity_Service.php b/tests/Test_Capacity_Service.php new file mode 100644 index 0000000..1a781e4 --- /dev/null +++ b/tests/Test_Capacity_Service.php @@ -0,0 +1,81 @@ +assertTrue(AgentCapacity::has_capacity(2, 3)); + } + + public function test_no_capacity_when_at_or_over_ceiling() + { + $this->assertFalse(AgentCapacity::has_capacity(3, 3)); + $this->assertFalse(AgentCapacity::has_capacity(4, 3)); + } + + // --------------------------------------------------------------------- + // load_percentage (pure) + // --------------------------------------------------------------------- + + public function test_load_percentage() + { + $this->assertEquals(30.0, AgentCapacity::load_percentage(3, 10)); + $this->assertEquals(25.0, AgentCapacity::load_percentage(2, 8)); + } + + public function test_load_percentage_zero_ceiling_is_full() + { + $this->assertEquals(100.0, AgentCapacity::load_percentage(0, 0)); + } + + // --------------------------------------------------------------------- + // Service flow (live wpdb) + // --------------------------------------------------------------------- + + public function test_can_accept_increment_and_decrement() + { + $service = new CapacityService; + $user_id = 4242; + + // Fresh agent (default ceiling 10) has capacity. + $this->assertTrue($service->can_accept_ticket($user_id)); + + // Fill to the ceiling. + for ($i = 0; $i < 10; $i++) { + $service->increment_load($user_id); + } + $this->assertFalse($service->can_accept_ticket($user_id)); + + // Releasing one frees capacity again. + $service->decrement_load($user_id); + $this->assertTrue($service->can_accept_ticket($user_id)); + } + + public function test_decrement_never_goes_negative() + { + $service = new CapacityService; + $user_id = 4343; + + // Decrementing a fresh (zero) row keeps it at zero. + $service->decrement_load($user_id); + + $row = AgentCapacity::for_user($user_id); + $this->assertNotNull($row); + $this->assertEquals(0, (int) $row->current_count); + } +}