Initial plugin commit
This commit is contained in:
98
includes/Repositories/AbstractRepository.php
Normal file
98
includes/Repositories/AbstractRepository.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* Shared repository helpers.
|
||||
*
|
||||
* @package KGV\VereinManager
|
||||
*/
|
||||
|
||||
namespace KGV\VereinManager\Repositories;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
abstract class AbstractRepository {
|
||||
|
||||
/**
|
||||
* WordPress database object.
|
||||
*
|
||||
* @var \wpdb
|
||||
*/
|
||||
protected $wpdb;
|
||||
|
||||
/**
|
||||
* Concrete table name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = '';
|
||||
|
||||
/**
|
||||
* Build repository instance.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
|
||||
$this->wpdb = $wpdb;
|
||||
$this->table = $this->resolve_table();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve table name in child class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function resolve_table();
|
||||
|
||||
/**
|
||||
* Find a single row by ID.
|
||||
*
|
||||
* @param int $id Record ID.
|
||||
* @return object|null
|
||||
*/
|
||||
public function find( $id ) {
|
||||
return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$this->table} WHERE id = %d", absint( $id ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a record.
|
||||
*
|
||||
* @param int $id Record ID.
|
||||
* @return bool
|
||||
*/
|
||||
public function delete( $id ) {
|
||||
$result = $this->wpdb->delete( $this->table, array( 'id' => absint( $id ) ), array( '%d' ) );
|
||||
return false !== $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current local datetime.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function now() {
|
||||
return current_time( 'mysql' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Restrict sortable columns.
|
||||
*
|
||||
* @param string $orderby Requested column.
|
||||
* @param array $allowed Allowed columns.
|
||||
* @param string $default Default column.
|
||||
* @return string
|
||||
*/
|
||||
protected function sanitize_orderby( $orderby, $allowed, $default ) {
|
||||
return in_array( $orderby, $allowed, true ) ? $orderby : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restrict sort direction.
|
||||
*
|
||||
* @param string $order Requested order.
|
||||
* @return string
|
||||
*/
|
||||
protected function sanitize_order( $order ) {
|
||||
return 'ASC' === strtoupper( (string) $order ) ? 'ASC' : 'DESC';
|
||||
}
|
||||
}
|
||||
279
includes/Repositories/AssignmentRepository.php
Normal file
279
includes/Repositories/AssignmentRepository.php
Normal file
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
/**
|
||||
* Parcel/member and parcel/tenant assignments.
|
||||
*
|
||||
* @package KGV\VereinManager
|
||||
*/
|
||||
|
||||
namespace KGV\VereinManager\Repositories;
|
||||
|
||||
use KGV\VereinManager\Roles;
|
||||
use KGV\VereinManager\Schema;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class AssignmentRepository {
|
||||
|
||||
/**
|
||||
* WordPress database object.
|
||||
*
|
||||
* @var \wpdb
|
||||
*/
|
||||
private $wpdb;
|
||||
|
||||
/**
|
||||
* Member assignment table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $member_table;
|
||||
|
||||
/**
|
||||
* Tenant assignment table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $tenant_table;
|
||||
|
||||
/**
|
||||
* Construct repository.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
|
||||
$this->wpdb = $wpdb;
|
||||
$this->member_table = Schema::table( 'parcel_members' );
|
||||
$this->tenant_table = Schema::table( 'parcel_tenants' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WordPress users with the role "Mitglied".
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_member_users() {
|
||||
$query = new \WP_User_Query(
|
||||
array(
|
||||
'role' => Roles::MEMBER_ROLE,
|
||||
'orderby' => 'display_name',
|
||||
'order' => 'ASC',
|
||||
'number' => 500,
|
||||
)
|
||||
);
|
||||
|
||||
return $query->get_results();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get assigned member IDs for a parcel.
|
||||
*
|
||||
* @param int $parcel_id Parcel ID.
|
||||
* @return array
|
||||
*/
|
||||
public function get_member_ids_for_parcel( $parcel_id ) {
|
||||
$ids = $this->wpdb->get_col( $this->wpdb->prepare( "SELECT user_id FROM {$this->member_table} WHERE parcel_id = %d", $parcel_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
return array_map( 'absint', $ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all members assigned to one parcel.
|
||||
*
|
||||
* @param int $parcel_id Parcel ID.
|
||||
* @return array
|
||||
*/
|
||||
public function get_members_for_parcel( $parcel_id ) {
|
||||
$users = $this->wpdb->users;
|
||||
$sql = "SELECT u.ID, u.display_name, u.user_email
|
||||
FROM {$this->member_table} pm
|
||||
INNER JOIN {$users} u ON u.ID = pm.user_id
|
||||
WHERE pm.parcel_id = %d
|
||||
ORDER BY u.display_name ASC";
|
||||
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $parcel_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all parcels assigned to a specific user.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @return array
|
||||
*/
|
||||
public function get_parcels_for_user( $user_id ) {
|
||||
$parcels = Schema::table( 'parcels' );
|
||||
$sections = Schema::table( 'sections' );
|
||||
$meters = Schema::table( 'meters' );
|
||||
|
||||
$sql = "SELECT p.*, s.name AS section_name,
|
||||
(SELECT meter_number FROM {$meters} wm WHERE wm.parcel_id = p.id AND wm.type = 'water' LIMIT 1) AS water_meter_number,
|
||||
(SELECT meter_number FROM {$meters} em WHERE em.parcel_id = p.id AND em.type = 'power' LIMIT 1) AS power_meter_number
|
||||
FROM {$this->member_table} pm
|
||||
INNER JOIN {$parcels} p ON p.id = pm.parcel_id
|
||||
LEFT JOIN {$sections} s ON s.id = p.section_id
|
||||
WHERE pm.user_id = %d
|
||||
ORDER BY s.name ASC, p.label ASC";
|
||||
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $user_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a parcel belongs to a given user.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param int $parcel_id Parcel ID.
|
||||
* @return bool
|
||||
*/
|
||||
public function user_has_parcel( $user_id, $parcel_id ) {
|
||||
$sql = "SELECT COUNT(*) FROM {$this->member_table} WHERE user_id = %d AND parcel_id = %d";
|
||||
return (int) $this->wpdb->get_var( $this->wpdb->prepare( $sql, $user_id, $parcel_id ) ) > 0; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Get assigned tenant IDs for a parcel.
|
||||
*
|
||||
* @param int $parcel_id Parcel ID.
|
||||
* @return array
|
||||
*/
|
||||
public function get_tenant_ids_for_parcel( $parcel_id ) {
|
||||
$ids = $this->wpdb->get_col( $this->wpdb->prepare( "SELECT tenant_id FROM {$this->tenant_table} WHERE parcel_id = %d", $parcel_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
return array_map( 'absint', $ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all members linked to a tenant via assigned parcels.
|
||||
*
|
||||
* @param int $tenant_id Tenant ID.
|
||||
* @return array
|
||||
*/
|
||||
public function get_members_for_tenant( $tenant_id ) {
|
||||
$parcels = Schema::table( 'parcels' );
|
||||
$users = $this->wpdb->users;
|
||||
$sql = "SELECT DISTINCT u.ID, u.display_name, u.user_email, p.label AS parcel_label
|
||||
FROM {$this->tenant_table} pt
|
||||
INNER JOIN {$parcels} p ON p.id = pt.parcel_id
|
||||
INNER JOIN {$this->member_table} pm ON pm.parcel_id = pt.parcel_id
|
||||
INNER JOIN {$users} u ON u.ID = pm.user_id
|
||||
WHERE pt.tenant_id = %d
|
||||
ORDER BY p.label ASC, u.display_name ASC";
|
||||
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $tenant_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tenants assigned to one parcel.
|
||||
*
|
||||
* @param int $parcel_id Parcel ID.
|
||||
* @return array
|
||||
*/
|
||||
public function get_tenants_for_parcel( $parcel_id ) {
|
||||
$tenants = Schema::table( 'tenants' );
|
||||
$sql = "SELECT t.*
|
||||
FROM {$this->tenant_table} pt
|
||||
INNER JOIN {$tenants} t ON t.id = pt.tenant_id
|
||||
WHERE pt.parcel_id = %d
|
||||
ORDER BY t.last_name ASC, t.first_name ASC";
|
||||
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $parcel_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all parcels assigned to one tenant.
|
||||
*
|
||||
* @param int $tenant_id Tenant ID.
|
||||
* @return array
|
||||
*/
|
||||
public function get_parcels_for_tenant( $tenant_id ) {
|
||||
$parcels = Schema::table( 'parcels' );
|
||||
$sections = Schema::table( 'sections' );
|
||||
$sql = "SELECT p.*, s.name AS section_name
|
||||
FROM {$this->tenant_table} pt
|
||||
INNER JOIN {$parcels} p ON p.id = pt.parcel_id
|
||||
LEFT JOIN {$sections} s ON s.id = p.section_id
|
||||
WHERE pt.tenant_id = %d
|
||||
ORDER BY s.name ASC, p.label ASC";
|
||||
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $tenant_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize member assignments.
|
||||
*
|
||||
* @param int $parcel_id Parcel ID.
|
||||
* @param array $user_ids User IDs.
|
||||
* @return void
|
||||
*/
|
||||
public function sync_member_ids( $parcel_id, $user_ids ) {
|
||||
$user_ids = array_values( array_unique( array_filter( array_map( 'absint', (array) $user_ids ) ) ) );
|
||||
$this->wpdb->delete( $this->member_table, array( 'parcel_id' => $parcel_id ), array( '%d' ) );
|
||||
|
||||
foreach ( $user_ids as $user_id ) {
|
||||
$this->wpdb->insert(
|
||||
$this->member_table,
|
||||
array(
|
||||
'parcel_id' => $parcel_id,
|
||||
'user_id' => $user_id,
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
),
|
||||
array( '%d', '%d', '%s' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize tenant assignments.
|
||||
*
|
||||
* @param int $parcel_id Parcel ID.
|
||||
* @param array $tenant_ids Tenant IDs.
|
||||
* @return void
|
||||
*/
|
||||
public function sync_tenant_ids( $parcel_id, $tenant_ids ) {
|
||||
$tenant_ids = array_values( array_unique( array_filter( array_map( 'absint', (array) $tenant_ids ) ) ) );
|
||||
$this->wpdb->delete( $this->tenant_table, array( 'parcel_id' => $parcel_id ), array( '%d' ) );
|
||||
|
||||
foreach ( $tenant_ids as $tenant_id ) {
|
||||
$this->wpdb->insert(
|
||||
$this->tenant_table,
|
||||
array(
|
||||
'parcel_id' => $parcel_id,
|
||||
'tenant_id' => $tenant_id,
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
),
|
||||
array( '%d', '%d', '%s' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a member is assigned to a different parcel.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param int $parcel_id Current parcel ID.
|
||||
* @return bool
|
||||
*/
|
||||
public function member_has_other_parcels( $user_id, $parcel_id ) {
|
||||
$sql = "SELECT COUNT(*) FROM {$this->member_table} WHERE user_id = %d AND parcel_id != %d";
|
||||
return (int) $this->wpdb->get_var( $this->wpdb->prepare( $sql, $user_id, $parcel_id ) ) > 0; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all assignments for a parcel.
|
||||
*
|
||||
* @param int $parcel_id Parcel ID.
|
||||
* @return void
|
||||
*/
|
||||
public function purge_parcel( $parcel_id ) {
|
||||
$this->wpdb->delete( $this->member_table, array( 'parcel_id' => $parcel_id ), array( '%d' ) );
|
||||
$this->wpdb->delete( $this->tenant_table, array( 'parcel_id' => $parcel_id ), array( '%d' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all tenant relations before deleting the tenant.
|
||||
*
|
||||
* @param int $tenant_id Tenant ID.
|
||||
* @return void
|
||||
*/
|
||||
public function purge_tenant( $tenant_id ) {
|
||||
$this->wpdb->delete( $this->tenant_table, array( 'tenant_id' => $tenant_id ), array( '%d' ) );
|
||||
}
|
||||
}
|
||||
117
includes/Repositories/ChatRepository.php
Normal file
117
includes/Repositories/ChatRepository.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* Chat repository for member communication.
|
||||
*
|
||||
* @package KGV\VereinManager
|
||||
*/
|
||||
|
||||
namespace KGV\VereinManager\Repositories;
|
||||
|
||||
use KGV\VereinManager\Schema;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class ChatRepository extends AbstractRepository {
|
||||
|
||||
/**
|
||||
* Resolve table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function resolve_table() {
|
||||
return Schema::table( 'chat_messages' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent chat messages for one room.
|
||||
*
|
||||
* @param string $room_key Room key.
|
||||
* @param int $limit Maximum result count.
|
||||
* @param int $after_id Optional lower ID boundary for polling.
|
||||
* @return array
|
||||
*/
|
||||
public function get_recent_messages( $room_key, $limit = 60, $after_id = 0 ) {
|
||||
$users = $this->wpdb->users;
|
||||
$room_key = sanitize_key( $room_key );
|
||||
$limit = max( 1, min( 200, absint( $limit ) ) );
|
||||
$after_id = absint( $after_id );
|
||||
|
||||
if ( $after_id > 0 ) {
|
||||
$sql = "SELECT m.*, u.display_name
|
||||
FROM {$this->table} m
|
||||
LEFT JOIN {$users} u ON u.ID = m.user_id
|
||||
WHERE m.room_key = %s AND m.id > %d
|
||||
ORDER BY m.id ASC
|
||||
LIMIT %d";
|
||||
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $room_key, $after_id, $limit ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
$sql = "SELECT *
|
||||
FROM (
|
||||
SELECT m.*, u.display_name
|
||||
FROM {$this->table} m
|
||||
LEFT JOIN {$users} u ON u.ID = m.user_id
|
||||
WHERE m.room_key = %s
|
||||
ORDER BY m.id DESC
|
||||
LIMIT %d
|
||||
) recent_messages
|
||||
ORDER BY id ASC";
|
||||
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $room_key, $limit ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Store one new message.
|
||||
*
|
||||
* @param string $room_key Room key.
|
||||
* @param int $user_id Sender user ID.
|
||||
* @param string $message Message body.
|
||||
* @return object|null
|
||||
*/
|
||||
public function save_message( $room_key, $user_id, $message ) {
|
||||
$room_key = sanitize_key( $room_key );
|
||||
$user_id = absint( $user_id );
|
||||
$message = trim( sanitize_textarea_field( $message ) );
|
||||
|
||||
if ( '' === $room_key || $user_id < 1 || '' === $message ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = $this->wpdb->insert(
|
||||
$this->table,
|
||||
array(
|
||||
'room_key' => $room_key,
|
||||
'user_id' => $user_id,
|
||||
'message' => $message,
|
||||
'created_at' => $this->now(),
|
||||
),
|
||||
array( '%s', '%d', '%s', '%s' )
|
||||
);
|
||||
|
||||
if ( false === $result ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->find_with_author( (int) $this->wpdb->insert_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Find one message together with the author's display name.
|
||||
*
|
||||
* @param int $id Message ID.
|
||||
* @return object|null
|
||||
*/
|
||||
private function find_with_author( $id ) {
|
||||
$users = $this->wpdb->users;
|
||||
$sql = "SELECT m.*, u.display_name
|
||||
FROM {$this->table} m
|
||||
LEFT JOIN {$users} u ON u.ID = m.user_id
|
||||
WHERE m.id = %d
|
||||
LIMIT 1";
|
||||
|
||||
return $this->wpdb->get_row( $this->wpdb->prepare( $sql, absint( $id ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
}
|
||||
313
includes/Repositories/CostRepository.php
Normal file
313
includes/Repositories/CostRepository.php
Normal file
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
/**
|
||||
* Cost repository.
|
||||
*
|
||||
* @package KGV\VereinManager
|
||||
*/
|
||||
|
||||
namespace KGV\VereinManager\Repositories;
|
||||
|
||||
use KGV\VereinManager\Schema;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class CostRepository extends AbstractRepository {
|
||||
|
||||
/**
|
||||
* Resolve table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function resolve_table() {
|
||||
return Schema::table( 'cost_entries' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the persistent year table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function year_table() {
|
||||
return Schema::table( 'cost_years' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the yearly section rates table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function rate_table() {
|
||||
return Schema::table( 'cost_rates' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Search cost entries.
|
||||
*
|
||||
* @param array $args Query arguments.
|
||||
* @return array
|
||||
*/
|
||||
public function search( $args = array() ) {
|
||||
$search = isset( $args['s'] ) ? sanitize_text_field( wp_unslash( $args['s'] ) ) : '';
|
||||
$year = isset( $args['year'] ) ? absint( $args['year'] ) : 0;
|
||||
$orderby = $this->sanitize_orderby( isset( $args['orderby'] ) ? sanitize_key( wp_unslash( $args['orderby'] ) ) : 'name', array( 'entry_year', 'name', 'distribution_type', 'unit_amount', 'total_cost', 'updated_at', 'created_at' ), 'name' );
|
||||
$order = $this->sanitize_order( isset( $args['order'] ) ? sanitize_key( wp_unslash( $args['order'] ) ) : 'ASC' );
|
||||
|
||||
$sql = "SELECT * FROM {$this->table} WHERE 1=1";
|
||||
$params = array();
|
||||
|
||||
if ( $year > 0 ) {
|
||||
$this->ensure_year( $year );
|
||||
$sql .= ' AND entry_year = %d';
|
||||
$params[] = $year;
|
||||
}
|
||||
|
||||
if ( '' !== $search ) {
|
||||
$like = '%' . $this->wpdb->esc_like( $search ) . '%';
|
||||
$sql .= ' AND (name LIKE %s OR note LIKE %s)';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY {$orderby} {$order}, id DESC";
|
||||
|
||||
if ( ! empty( $params ) ) {
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
return $this->wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or update a cost entry.
|
||||
*
|
||||
* @param array $data Entry data.
|
||||
* @param int $id Optional ID.
|
||||
* @return int|false
|
||||
*/
|
||||
public function save( $data, $id = 0 ) {
|
||||
$payload = array(
|
||||
'entry_year' => absint( $data['entry_year'] ),
|
||||
'name' => $data['name'],
|
||||
'distribution_type' => isset( $data['distribution_type'] ) ? $data['distribution_type'] : 'parcel',
|
||||
'unit_amount' => isset( $data['unit_amount'] ) ? (float) $data['unit_amount'] : 0,
|
||||
'total_cost' => (float) $data['total_cost'],
|
||||
'note' => $data['note'],
|
||||
'updated_at' => $this->now(),
|
||||
);
|
||||
|
||||
$formats = array( '%d', '%s', '%s', '%f', '%f', '%s', '%s' );
|
||||
|
||||
$this->ensure_year( $payload['entry_year'] );
|
||||
|
||||
if ( $id > 0 ) {
|
||||
$this->wpdb->update( $this->table, $payload, array( 'id' => $id ), $formats, array( '%d' ) );
|
||||
return $id;
|
||||
}
|
||||
|
||||
$payload['created_at'] = $this->now();
|
||||
$this->wpdb->insert( $this->table, $payload, array( '%d', '%s', '%s', '%f', '%f', '%s', '%s', '%s' ) );
|
||||
|
||||
return $this->wpdb->insert_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available years for filters and dropdowns.
|
||||
*
|
||||
* @param int $selected_year Optional selected year.
|
||||
* @return array
|
||||
*/
|
||||
public function get_years( $selected_year = 0 ) {
|
||||
$year_table = $this->year_table();
|
||||
$years = $this->wpdb->get_col( "SELECT DISTINCT entry_year FROM {$this->table} WHERE entry_year > 0 ORDER BY entry_year DESC" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$saved_years = $this->wpdb->get_col( "SELECT entry_year FROM {$year_table} ORDER BY entry_year DESC" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$current_year = (int) current_time( 'Y' );
|
||||
$years = array_map( 'absint', array_merge( (array) $years, (array) $saved_years ) );
|
||||
$years[] = $current_year;
|
||||
$years[] = $current_year - 1;
|
||||
$years[] = $current_year + 1;
|
||||
|
||||
if ( $selected_year > 0 ) {
|
||||
$years[] = absint( $selected_year );
|
||||
}
|
||||
|
||||
$years = array_values( array_unique( array_filter( $years ) ) );
|
||||
rsort( $years, SORT_NUMERIC );
|
||||
|
||||
return $years;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure one year exists so it remains selectable without cost entries.
|
||||
*
|
||||
* @param int $year Year value.
|
||||
* @return bool
|
||||
*/
|
||||
public function ensure_year( $year ) {
|
||||
$year = absint( $year );
|
||||
$year_table = $this->year_table();
|
||||
|
||||
if ( $year < 1 || '' === $year_table ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$exists = (int) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COUNT(*) FROM {$year_table} WHERE entry_year = %d", $year ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
|
||||
if ( $exists > 0 ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$result = $this->wpdb->insert(
|
||||
$year_table,
|
||||
array(
|
||||
'entry_year' => $year,
|
||||
'power_price_per_kwh' => null,
|
||||
'water_price_per_m3' => null,
|
||||
'created_at' => $this->now(),
|
||||
'updated_at' => $this->now(),
|
||||
),
|
||||
array( '%d', '%s', '%s', '%s', '%s' )
|
||||
);
|
||||
|
||||
return false !== $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the configured yearly prices for electricity and water.
|
||||
*
|
||||
* @param int $year Year value.
|
||||
* @param float|null $power_price_per_kwh Price per kWh.
|
||||
* @param float|null $water_price_per_m3 Price per m³.
|
||||
* @return bool
|
||||
*/
|
||||
public function save_year( $year, $power_price_per_kwh = null, $water_price_per_m3 = null ) {
|
||||
$year = absint( $year );
|
||||
$year_table = $this->year_table();
|
||||
|
||||
if ( ! $this->ensure_year( $year ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = $this->wpdb->update(
|
||||
$year_table,
|
||||
array(
|
||||
'power_price_per_kwh' => null !== $power_price_per_kwh ? (float) $power_price_per_kwh : null,
|
||||
'water_price_per_m3' => null !== $water_price_per_m3 ? (float) $water_price_per_m3 : null,
|
||||
'updated_at' => $this->now(),
|
||||
),
|
||||
array( 'entry_year' => $year ),
|
||||
array( '%f', '%f', '%s' ),
|
||||
array( '%d' )
|
||||
);
|
||||
|
||||
return false !== $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load one year's saved price details.
|
||||
*
|
||||
* @param int $year Selected year.
|
||||
* @return object|null
|
||||
*/
|
||||
public function get_year_details( $year ) {
|
||||
$year = absint( $year );
|
||||
$year_table = $this->year_table();
|
||||
|
||||
if ( $year < 1 || '' === $year_table ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$year_table} WHERE entry_year = %d", $year ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Save section-specific yearly prices.
|
||||
*
|
||||
* @param array $data Section pricing data.
|
||||
* @return bool
|
||||
*/
|
||||
public function save_section_prices( $data ) {
|
||||
$year = absint( $data['entry_year'] );
|
||||
$section_id = absint( $data['section_id'] );
|
||||
$rate_table = $this->rate_table();
|
||||
|
||||
if ( ! $this->ensure_year( $year ) || $section_id < 1 || '' === $rate_table ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$payload = array(
|
||||
'entry_year' => $year,
|
||||
'section_id' => $section_id,
|
||||
'power_price_per_kwh' => null !== $data['power_price_per_kwh'] ? (float) $data['power_price_per_kwh'] : null,
|
||||
'water_price_per_m3' => null !== $data['water_price_per_m3'] ? (float) $data['water_price_per_m3'] : null,
|
||||
'updated_at' => $this->now(),
|
||||
);
|
||||
|
||||
$exists = (int) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COUNT(*) FROM {$rate_table} WHERE entry_year = %d AND section_id = %d", $year, $section_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
|
||||
if ( $exists > 0 ) {
|
||||
$result = $this->wpdb->update(
|
||||
$rate_table,
|
||||
array(
|
||||
'power_price_per_kwh' => $payload['power_price_per_kwh'],
|
||||
'water_price_per_m3' => $payload['water_price_per_m3'],
|
||||
'updated_at' => $payload['updated_at'],
|
||||
),
|
||||
array(
|
||||
'entry_year' => $year,
|
||||
'section_id' => $section_id,
|
||||
),
|
||||
array( '%f', '%f', '%s' ),
|
||||
array( '%d', '%d' )
|
||||
);
|
||||
|
||||
return false !== $result;
|
||||
}
|
||||
|
||||
$payload['created_at'] = $this->now();
|
||||
$result = $this->wpdb->insert( $rate_table, $payload, array( '%d', '%d', '%f', '%f', '%s', '%s' ) );
|
||||
|
||||
return false !== $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all saved section prices for one year.
|
||||
*
|
||||
* @param int $year Selected year.
|
||||
* @return array
|
||||
*/
|
||||
public function get_section_prices( $year ) {
|
||||
$year = absint( $year );
|
||||
$rate_table = $this->rate_table();
|
||||
$sections = Schema::table( 'sections' );
|
||||
|
||||
if ( $year < 1 || '' === $rate_table ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$sql = "SELECT r.*, s.name AS section_name
|
||||
FROM {$rate_table} r
|
||||
LEFT JOIN {$sections} s ON s.id = r.section_id
|
||||
WHERE r.entry_year = %d
|
||||
ORDER BY s.name ASC, r.section_id ASC";
|
||||
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $year ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum all costs for a year.
|
||||
*
|
||||
* @param int $year Selected year.
|
||||
* @return float
|
||||
*/
|
||||
public function get_total_for_year( $year ) {
|
||||
if ( $year < 1 ) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$total = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COALESCE(SUM(total_cost), 0) FROM {$this->table} WHERE entry_year = %d", absint( $year ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
|
||||
return (float) $total;
|
||||
}
|
||||
}
|
||||
247
includes/Repositories/MeterReadingRepository.php
Normal file
247
includes/Repositories/MeterReadingRepository.php
Normal file
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
/**
|
||||
* Meter reading repository.
|
||||
*
|
||||
* @package KGV\VereinManager
|
||||
*/
|
||||
|
||||
namespace KGV\VereinManager\Repositories;
|
||||
|
||||
use KGV\VereinManager\Schema;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class MeterReadingRepository extends AbstractRepository {
|
||||
|
||||
/**
|
||||
* Resolve table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function resolve_table() {
|
||||
return Schema::table( 'meter_readings' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new reading.
|
||||
*
|
||||
* @param array $data Reading data.
|
||||
* @return int|false
|
||||
*/
|
||||
public function save( $data ) {
|
||||
$payload = array(
|
||||
'meter_id' => absint( $data['meter_id'] ),
|
||||
'parcel_id' => absint( $data['parcel_id'] ),
|
||||
'reading_value' => (float) $data['reading_value'],
|
||||
'reading_date' => $data['reading_date'],
|
||||
'note' => $data['note'],
|
||||
'submitted_by' => absint( $data['submitted_by'] ),
|
||||
'is_self_reading' => ! empty( $data['is_self_reading'] ) ? 1 : 0,
|
||||
'created_at' => $this->now(),
|
||||
);
|
||||
|
||||
$this->wpdb->insert(
|
||||
$this->table,
|
||||
$payload,
|
||||
array( '%d', '%d', '%f', '%s', '%s', '%d', '%d', '%s' )
|
||||
);
|
||||
|
||||
return $this->wpdb->insert_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest reading for one meter.
|
||||
*
|
||||
* @param int $meter_id Meter ID.
|
||||
* @return object|null
|
||||
*/
|
||||
public function get_latest_for_meter( $meter_id ) {
|
||||
$sql = "SELECT * FROM {$this->table} WHERE meter_id = %d ORDER BY reading_date DESC, id DESC LIMIT 1";
|
||||
return $this->wpdb->get_row( $this->wpdb->prepare( $sql, $meter_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent readings for a parcel.
|
||||
*
|
||||
* @param int $parcel_id Parcel ID.
|
||||
* @param int $limit Number of rows.
|
||||
* @return array
|
||||
*/
|
||||
public function get_recent_for_parcel( $parcel_id, $limit = 10 ) {
|
||||
$meters = Schema::table( 'meters' );
|
||||
$sql = "SELECT r.*, m.type, m.meter_number
|
||||
FROM {$this->table} r
|
||||
LEFT JOIN {$meters} m ON m.id = r.meter_id
|
||||
WHERE r.parcel_id = %d
|
||||
ORDER BY r.reading_date DESC, r.id DESC
|
||||
LIMIT %d";
|
||||
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $parcel_id, absint( $limit ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a filtered consumption report including deltas since the previous reading.
|
||||
*
|
||||
* @param int $section_id Optional section filter.
|
||||
* @param string $date_from Optional start date.
|
||||
* @param string $date_to Optional end date.
|
||||
* @param string $order ASC|DESC by reading date.
|
||||
* @return array
|
||||
*/
|
||||
public function get_consumption_report( $section_id = 0, $date_from = '', $date_to = '', $order = 'DESC' ) {
|
||||
$meters = Schema::table( 'meters' );
|
||||
$sections = Schema::table( 'sections' );
|
||||
$parcels = Schema::table( 'parcels' );
|
||||
$sql = "SELECT r.*, m.type, m.meter_number, m.section_id, s.name AS section_name, p.label AS parcel_label
|
||||
FROM {$this->table} r
|
||||
LEFT JOIN {$meters} m ON m.id = r.meter_id
|
||||
LEFT JOIN {$sections} s ON s.id = m.section_id
|
||||
LEFT JOIN {$parcels} p ON p.id = r.parcel_id
|
||||
WHERE 1=1";
|
||||
$params = array();
|
||||
$order = 'ASC' === strtoupper( (string) $order ) ? 'ASC' : 'DESC';
|
||||
|
||||
if ( $section_id > 0 ) {
|
||||
$sql .= ' AND m.section_id = %d';
|
||||
$params[] = $section_id;
|
||||
}
|
||||
|
||||
if ( '' !== $date_to ) {
|
||||
$sql .= ' AND r.reading_date <= %s';
|
||||
$params[] = $date_to;
|
||||
}
|
||||
|
||||
$sql .= ' ORDER BY r.meter_id ASC, r.reading_date ASC, r.id ASC';
|
||||
|
||||
$rows = ! empty( $params )
|
||||
? $this->wpdb->get_results( $this->wpdb->prepare( $sql, $params ) ) // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
: $this->wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
|
||||
$report = array();
|
||||
$last_by_meter = array();
|
||||
|
||||
foreach ( $rows as $row ) {
|
||||
$previous = isset( $last_by_meter[ $row->meter_id ] ) ? $last_by_meter[ $row->meter_id ] : null;
|
||||
$row->previous_value = $previous ? (float) $previous->reading_value : null;
|
||||
$row->consumption = $previous && (float) $row->reading_value >= (float) $previous->reading_value ? (float) $row->reading_value - (float) $previous->reading_value : null;
|
||||
$last_by_meter[$row->meter_id] = $row;
|
||||
|
||||
if ( '' !== $date_from && $row->reading_date < $date_from ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$report[] = $row;
|
||||
}
|
||||
|
||||
usort(
|
||||
$report,
|
||||
function( $left, $right ) use ( $order ) {
|
||||
$left_key = $left->reading_date . ' ' . str_pad( (string) $left->id, 10, '0', STR_PAD_LEFT );
|
||||
$right_key = $right->reading_date . ' ' . str_pad( (string) $right->id, 10, '0', STR_PAD_LEFT );
|
||||
|
||||
if ( $left_key === $right_key ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( 'ASC' === $order ) {
|
||||
return $left_key < $right_key ? -1 : 1;
|
||||
}
|
||||
|
||||
return $left_key > $right_key ? -1 : 1;
|
||||
}
|
||||
);
|
||||
|
||||
return $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all readings for a parcel, suitable for export.
|
||||
*
|
||||
* @param int $parcel_id Parcel ID.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_for_parcel( $parcel_id ) {
|
||||
$meters = Schema::table( 'meters' );
|
||||
$sql = "SELECT r.*, m.type, m.meter_number
|
||||
FROM {$this->table} r
|
||||
LEFT JOIN {$meters} m ON m.id = r.meter_id
|
||||
WHERE r.parcel_id = %d
|
||||
ORDER BY r.reading_date DESC, r.id DESC";
|
||||
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $parcel_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reading history for a specific meter.
|
||||
*
|
||||
* @param int $meter_id Meter ID.
|
||||
* @param int $limit Max rows.
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_for_meter( $meter_id, $limit = 50 ) {
|
||||
$parcels = Schema::table( 'parcels' );
|
||||
$sql = "SELECT r.*, p.label AS parcel_label
|
||||
FROM {$this->table} r
|
||||
LEFT JOIN {$parcels} p ON p.id = r.parcel_id
|
||||
WHERE r.meter_id = %d
|
||||
ORDER BY r.reading_date DESC, r.id DESC
|
||||
LIMIT %d";
|
||||
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $meter_id, absint( $limit ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a monthly summary for one meter.
|
||||
*
|
||||
* @param int $meter_id Meter ID.
|
||||
* @return array
|
||||
*/
|
||||
public function get_monthly_summary_for_meter( $meter_id ) {
|
||||
$rows = $this->wpdb->get_results(
|
||||
$this->wpdb->prepare(
|
||||
"SELECT * FROM {$this->table} WHERE meter_id = %d ORDER BY reading_date ASC, id ASC",
|
||||
$meter_id
|
||||
)
|
||||
); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
|
||||
if ( empty( $rows ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$monthly = array();
|
||||
$last = null;
|
||||
|
||||
foreach ( $rows as $row ) {
|
||||
$key = gmdate( 'Y-m', strtotime( $row->reading_date ) );
|
||||
|
||||
if ( ! isset( $monthly[ $key ] ) ) {
|
||||
$monthly[ $key ] = array(
|
||||
'month' => $key,
|
||||
'from_value' => null,
|
||||
'to_value' => (float) $row->reading_value,
|
||||
'consumption' => 0.0,
|
||||
'readings' => 0,
|
||||
'last_date' => $row->reading_date,
|
||||
);
|
||||
}
|
||||
|
||||
$monthly[ $key ]['to_value'] = (float) $row->reading_value;
|
||||
$monthly[ $key ]['last_date'] = $row->reading_date;
|
||||
$monthly[ $key ]['readings']++;
|
||||
|
||||
if ( $last && (float) $row->reading_value >= (float) $last->reading_value ) {
|
||||
if ( null === $monthly[ $key ]['from_value'] ) {
|
||||
$monthly[ $key ]['from_value'] = (float) $last->reading_value;
|
||||
}
|
||||
|
||||
$monthly[ $key ]['consumption'] += (float) $row->reading_value - (float) $last->reading_value;
|
||||
}
|
||||
|
||||
$last = $row;
|
||||
}
|
||||
|
||||
return array_reverse( array_values( $monthly ) );
|
||||
}
|
||||
}
|
||||
304
includes/Repositories/MeterRepository.php
Normal file
304
includes/Repositories/MeterRepository.php
Normal file
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
/**
|
||||
* Meter repository.
|
||||
*
|
||||
* @package KGV\VereinManager
|
||||
*/
|
||||
|
||||
namespace KGV\VereinManager\Repositories;
|
||||
|
||||
use KGV\VereinManager\Schema;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class MeterRepository extends AbstractRepository {
|
||||
|
||||
/**
|
||||
* Resolve table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function resolve_table() {
|
||||
return Schema::table( 'meters' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Search meters.
|
||||
*
|
||||
* @param array $args Query arguments.
|
||||
* @return array
|
||||
*/
|
||||
public function search( $args = array() ) {
|
||||
$search = isset( $args['s'] ) ? sanitize_text_field( wp_unslash( $args['s'] ) ) : '';
|
||||
$type = isset( $args['type'] ) ? sanitize_key( wp_unslash( $args['type'] ) ) : '';
|
||||
$section_id = isset( $args['section_id'] ) ? absint( $args['section_id'] ) : 0;
|
||||
$assignment = isset( $args['assignment'] ) ? sanitize_key( wp_unslash( $args['assignment'] ) ) : '';
|
||||
$orderby = $this->sanitize_orderby( isset( $args['orderby'] ) ? sanitize_key( wp_unslash( $args['orderby'] ) ) : 'meter_number', array( 'meter_number', 'type', 'installed_at', 'calibration_year', 'is_active', 'created_at' ), 'meter_number' );
|
||||
$order = $this->sanitize_order( isset( $args['order'] ) ? sanitize_key( wp_unslash( $args['order'] ) ) : 'ASC' );
|
||||
|
||||
$sections = Schema::table( 'sections' );
|
||||
$parcels = Schema::table( 'parcels' );
|
||||
|
||||
$sql = "SELECT m.*, s.name AS section_name, p.label AS parcel_label
|
||||
FROM {$this->table} m
|
||||
LEFT JOIN {$sections} s ON s.id = m.section_id
|
||||
LEFT JOIN {$parcels} p ON p.id = m.parcel_id
|
||||
WHERE 1=1";
|
||||
|
||||
$params = array();
|
||||
|
||||
if ( '' !== $search ) {
|
||||
$like = '%' . $this->wpdb->esc_like( $search ) . '%';
|
||||
$sql .= ' AND (m.meter_number LIKE %s OR m.note LIKE %s)';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
}
|
||||
|
||||
if ( in_array( $type, array( 'water', 'power' ), true ) ) {
|
||||
$sql .= ' AND m.type = %s';
|
||||
$params[] = $type;
|
||||
}
|
||||
|
||||
if ( $section_id > 0 ) {
|
||||
$sql .= ' AND m.section_id = %d';
|
||||
$params[] = $section_id;
|
||||
}
|
||||
|
||||
if ( 'free' === $assignment ) {
|
||||
$sql .= ' AND m.parcel_id IS NULL AND m.is_main_meter = 0';
|
||||
} elseif ( 'assigned' === $assignment ) {
|
||||
$sql .= ' AND m.parcel_id IS NOT NULL';
|
||||
} elseif ( 'main' === $assignment ) {
|
||||
$sql .= ' AND m.is_main_meter = 1';
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY m.{$orderby} {$order}, m.id DESC";
|
||||
|
||||
if ( ! empty( $params ) ) {
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
return $this->wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or update meter.
|
||||
*
|
||||
* @param array $data Meter data.
|
||||
* @param int $id Optional ID.
|
||||
* @return int|false
|
||||
*/
|
||||
public function save( $data, $id = 0 ) {
|
||||
$current = $id > 0 ? $this->find( $id ) : null;
|
||||
$is_main_meter = isset( $data['is_main_meter'] ) ? absint( $data['is_main_meter'] ) : ( $current ? (int) $current->is_main_meter : 0 );
|
||||
|
||||
if ( empty( $data['is_active'] ) ) {
|
||||
$is_main_meter = 0;
|
||||
}
|
||||
|
||||
$payload = array(
|
||||
'type' => $data['type'],
|
||||
'meter_number' => $data['meter_number'],
|
||||
'section_id' => $data['section_id'],
|
||||
'parcel_id' => $current ? $current->parcel_id : null,
|
||||
'installed_at' => $data['installed_at'] ? $data['installed_at'] : null,
|
||||
'calibration_year' => isset( $data['calibration_year'] ) ? $data['calibration_year'] : null,
|
||||
'is_main_meter' => $is_main_meter,
|
||||
'is_active' => $data['is_active'],
|
||||
'note' => $data['note'],
|
||||
'updated_at' => $this->now(),
|
||||
);
|
||||
|
||||
$formats = array( '%s', '%s', '%d', '%s', '%s', '%d', '%d', '%d', '%s', '%s' );
|
||||
|
||||
if ( $id > 0 ) {
|
||||
$this->wpdb->update( $this->table, $payload, array( 'id' => $id ), $formats, array( '%d' ) );
|
||||
return $id;
|
||||
}
|
||||
|
||||
$payload['created_at'] = $this->now();
|
||||
$this->wpdb->insert( $this->table, $payload, array( '%s', '%s', '%d', '%s', '%s', '%d', '%d', '%d', '%s', '%s', '%s' ) );
|
||||
|
||||
return $this->wpdb->insert_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a meter number already exists for the same type.
|
||||
*
|
||||
* @param string $meter_number Meter number.
|
||||
* @param string $type Meter type.
|
||||
* @param int $exclude_id Optional ID to exclude.
|
||||
* @return bool
|
||||
*/
|
||||
public function meter_number_exists( $meter_number, $type, $exclude_id = 0 ) {
|
||||
$sql = "SELECT COUNT(*) FROM {$this->table} WHERE meter_number = %s AND type = %s";
|
||||
$params = array( $meter_number, $type );
|
||||
|
||||
if ( $exclude_id > 0 ) {
|
||||
$sql .= ' AND id != %d';
|
||||
$params[] = $exclude_id;
|
||||
}
|
||||
|
||||
return (int) $this->wpdb->get_var( $this->wpdb->prepare( $sql, $params ) ) > 0; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Return free meters for selection.
|
||||
*
|
||||
* @param string $type water|power.
|
||||
* @param int $section_id Optional section filter.
|
||||
* @param int $current_parcel_id Current parcel in edit mode.
|
||||
* @return array
|
||||
*/
|
||||
public function get_free_by_type( $type, $section_id = 0, $current_parcel_id = 0 ) {
|
||||
$sql = "SELECT * FROM {$this->table} WHERE type = %s AND is_active = 1 AND is_main_meter = 0 AND (parcel_id IS NULL OR parcel_id = %d)";
|
||||
$params = array( $type, $current_parcel_id );
|
||||
|
||||
if ( $section_id > 0 ) {
|
||||
$sql .= ' AND section_id = %d';
|
||||
$params[] = $section_id;
|
||||
}
|
||||
|
||||
$sql .= ' ORDER BY meter_number ASC';
|
||||
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the meter assigned to a parcel by type.
|
||||
*
|
||||
* @param int $parcel_id Parcel ID.
|
||||
* @param string $type Meter type.
|
||||
* @return object|null
|
||||
*/
|
||||
public function get_assigned_to_parcel( $parcel_id, $type ) {
|
||||
return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$this->table} WHERE parcel_id = %d AND type = %s LIMIT 1", $parcel_id, $type ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a meter if it can legally be assigned to a parcel.
|
||||
*
|
||||
* @param int $id Meter ID.
|
||||
* @param string $type Expected type.
|
||||
* @param int $section_id Target section ID.
|
||||
* @param int $current_parcel_id Current parcel ID.
|
||||
* @return object|null
|
||||
*/
|
||||
public function get_assignable_meter( $id, $type, $section_id, $current_parcel_id ) {
|
||||
$sql = "SELECT * FROM {$this->table} WHERE id = %d AND type = %s AND section_id = %d AND is_active = 1 AND is_main_meter = 0 AND (parcel_id IS NULL OR parcel_id = %d) LIMIT 1";
|
||||
return $this->wpdb->get_row( $this->wpdb->prepare( $sql, $id, $type, $section_id, $current_parcel_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all meters assigned to a parcel.
|
||||
*
|
||||
* @param int $parcel_id Parcel ID.
|
||||
* @return void
|
||||
*/
|
||||
public function release_parcel( $parcel_id ) {
|
||||
$this->wpdb->query( $this->wpdb->prepare( "UPDATE {$this->table} SET parcel_id = NULL, updated_at = %s WHERE parcel_id = %d", $this->now(), $parcel_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a meter to a parcel.
|
||||
*
|
||||
* @param int $meter_id Meter ID.
|
||||
* @param int $parcel_id Parcel ID.
|
||||
* @return void
|
||||
*/
|
||||
public function assign_to_parcel( $meter_id, $parcel_id ) {
|
||||
$this->wpdb->update(
|
||||
$this->table,
|
||||
array(
|
||||
'parcel_id' => $parcel_id,
|
||||
'is_main_meter' => 0,
|
||||
'updated_at' => $this->now(),
|
||||
),
|
||||
array( 'id' => $meter_id ),
|
||||
array( '%d', '%d', '%s' ),
|
||||
array( '%d' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release one specific meter from its parcel.
|
||||
*
|
||||
* @param int $meter_id Meter ID.
|
||||
* @return void
|
||||
*/
|
||||
public function release_meter( $meter_id ) {
|
||||
$this->wpdb->update(
|
||||
$this->table,
|
||||
array(
|
||||
'parcel_id' => null,
|
||||
'updated_at' => $this->now(),
|
||||
),
|
||||
array( 'id' => $meter_id ),
|
||||
array( '%d', '%s' ),
|
||||
array( '%d' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all available main meters for one section.
|
||||
*
|
||||
* @param int $section_id Section ID.
|
||||
* @return array
|
||||
*/
|
||||
public function get_available_main_for_section( $section_id ) {
|
||||
$sql = "SELECT * FROM {$this->table} WHERE section_id = %d AND is_active = 1 AND parcel_id IS NULL ORDER BY type ASC, meter_number ASC";
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $section_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all selected main meters for one section.
|
||||
*
|
||||
* @param int $section_id Section ID.
|
||||
* @return array
|
||||
*/
|
||||
public function get_main_for_section( $section_id ) {
|
||||
$sql = "SELECT * FROM {$this->table} WHERE section_id = %d AND is_main_meter = 1 ORDER BY type ASC, meter_number ASC";
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $section_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync selected main meters for a section.
|
||||
*
|
||||
* @param int $section_id Section ID.
|
||||
* @param array $meter_ids Selected meter IDs.
|
||||
* @return void
|
||||
*/
|
||||
public function sync_main_meters_for_section( $section_id, $meter_ids ) {
|
||||
$section_id = absint( $section_id );
|
||||
$meter_ids = array_values( array_unique( array_filter( array_map( 'absint', (array) $meter_ids ) ) ) );
|
||||
|
||||
if ( $section_id < 1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->wpdb->query( $this->wpdb->prepare( "UPDATE {$this->table} SET is_main_meter = 0, updated_at = %s WHERE section_id = %d AND parcel_id IS NULL", $this->now(), $section_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
|
||||
if ( empty( $meter_ids ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$placeholders = implode( ', ', array_fill( 0, count( $meter_ids ), '%d' ) );
|
||||
$params = array_merge( array( $this->now(), $section_id ), $meter_ids );
|
||||
$sql = "UPDATE {$this->table} SET is_main_meter = 1, parcel_id = NULL, updated_at = %s WHERE section_id = %d AND is_active = 1 AND parcel_id IS NULL AND id IN ({$placeholders})";
|
||||
|
||||
$this->wpdb->query( $this->wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the meter is already assigned.
|
||||
*
|
||||
* @param int $id Meter ID.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_assigned( $id ) {
|
||||
$meter = $this->find( $id );
|
||||
return $meter && ! empty( $meter->parcel_id );
|
||||
}
|
||||
}
|
||||
195
includes/Repositories/ParcelRepository.php
Normal file
195
includes/Repositories/ParcelRepository.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* Parcel repository.
|
||||
*
|
||||
* @package KGV\VereinManager
|
||||
*/
|
||||
|
||||
namespace KGV\VereinManager\Repositories;
|
||||
|
||||
use KGV\VereinManager\Schema;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class ParcelRepository extends AbstractRepository {
|
||||
|
||||
/**
|
||||
* Resolve table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function resolve_table() {
|
||||
return Schema::table( 'parcels' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Search parcels with relation information.
|
||||
*
|
||||
* @param array $args Query arguments.
|
||||
* @return array
|
||||
*/
|
||||
public function search( $args = array() ) {
|
||||
$search = isset( $args['s'] ) ? sanitize_text_field( wp_unslash( $args['s'] ) ) : '';
|
||||
$status = isset( $args['status'] ) ? sanitize_key( wp_unslash( $args['status'] ) ) : '';
|
||||
$section_id = isset( $args['section_id'] ) ? absint( $args['section_id'] ) : 0;
|
||||
$orderby = $this->sanitize_orderby( isset( $args['orderby'] ) ? sanitize_key( wp_unslash( $args['orderby'] ) ) : 'label', array( 'label', 'status', 'area', 'annual_rent', 'created_at' ), 'label' );
|
||||
$order = $this->sanitize_order( isset( $args['order'] ) ? sanitize_key( wp_unslash( $args['order'] ) ) : 'ASC' );
|
||||
$limit = isset( $args['limit'] ) ? absint( $args['limit'] ) : 0;
|
||||
$paged = isset( $args['paged'] ) ? max( 1, absint( $args['paged'] ) ) : 1;
|
||||
$offset = $limit > 0 ? ( $paged - 1 ) * $limit : 0;
|
||||
|
||||
$meters = Schema::table( 'meters' );
|
||||
$parcel_members = Schema::table( 'parcel_members' );
|
||||
$parcel_tenants = Schema::table( 'parcel_tenants' );
|
||||
$sections = Schema::table( 'sections' );
|
||||
|
||||
$sql = "SELECT p.*, s.name AS section_name,
|
||||
MAX(CASE WHEN m.type = 'water' THEN m.meter_number ELSE NULL END) AS water_meter_number,
|
||||
MAX(CASE WHEN m.type = 'power' THEN m.meter_number ELSE NULL END) AS power_meter_number,
|
||||
COUNT(DISTINCT pm.user_id) AS member_count,
|
||||
COUNT(DISTINCT pt.tenant_id) AS tenant_count
|
||||
FROM {$this->table} p
|
||||
LEFT JOIN {$sections} s ON s.id = p.section_id
|
||||
LEFT JOIN {$meters} m ON m.parcel_id = p.id
|
||||
LEFT JOIN {$parcel_members} pm ON pm.parcel_id = p.id
|
||||
LEFT JOIN {$parcel_tenants} pt ON pt.parcel_id = p.id
|
||||
WHERE 1=1";
|
||||
|
||||
$params = array();
|
||||
|
||||
if ( '' !== $search ) {
|
||||
$like = '%' . $this->wpdb->esc_like( $search ) . '%';
|
||||
$sql .= ' AND (p.label LIKE %s OR p.note LIKE %s)';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
}
|
||||
|
||||
if ( in_array( $status, array( 'free', 'assigned', 'reserved', 'inactive' ), true ) ) {
|
||||
$sql .= ' AND p.status = %s';
|
||||
$params[] = $status;
|
||||
}
|
||||
|
||||
if ( $section_id > 0 ) {
|
||||
$sql .= ' AND p.section_id = %d';
|
||||
$params[] = $section_id;
|
||||
}
|
||||
|
||||
$sql .= " GROUP BY p.id ORDER BY p.{$orderby} {$order}, p.id DESC";
|
||||
|
||||
if ( $limit > 0 ) {
|
||||
$sql .= ' LIMIT %d';
|
||||
$params[] = $limit;
|
||||
|
||||
if ( $offset > 0 ) {
|
||||
$sql .= ' OFFSET %d';
|
||||
$params[] = $offset;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $params ) ) {
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
return $this->wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Count parcels for the current filter set.
|
||||
*
|
||||
* @param array $args Query arguments.
|
||||
* @return int
|
||||
*/
|
||||
public function count_filtered( $args = array() ) {
|
||||
$search = isset( $args['s'] ) ? sanitize_text_field( wp_unslash( $args['s'] ) ) : '';
|
||||
$status = isset( $args['status'] ) ? sanitize_key( wp_unslash( $args['status'] ) ) : '';
|
||||
$section_id = isset( $args['section_id'] ) ? absint( $args['section_id'] ) : 0;
|
||||
$sql = "SELECT COUNT(*) FROM {$this->table} p WHERE 1=1";
|
||||
$params = array();
|
||||
|
||||
if ( '' !== $search ) {
|
||||
$like = '%' . $this->wpdb->esc_like( $search ) . '%';
|
||||
$sql .= ' AND (p.label LIKE %s OR p.note LIKE %s)';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
}
|
||||
|
||||
if ( in_array( $status, array( 'free', 'assigned', 'reserved', 'inactive' ), true ) ) {
|
||||
$sql .= ' AND p.status = %s';
|
||||
$params[] = $status;
|
||||
}
|
||||
|
||||
if ( $section_id > 0 ) {
|
||||
$sql .= ' AND p.section_id = %d';
|
||||
$params[] = $section_id;
|
||||
}
|
||||
|
||||
if ( ! empty( $params ) ) {
|
||||
return (int) $this->wpdb->get_var( $this->wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
return (int) $this->wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or update parcel.
|
||||
*
|
||||
* @param array $data Parcel data.
|
||||
* @param int $id Optional parcel ID.
|
||||
* @return int|false
|
||||
*/
|
||||
public function save( $data, $id = 0 ) {
|
||||
$payload = array(
|
||||
'label' => $data['label'],
|
||||
'section_id' => $data['section_id'],
|
||||
'area' => $data['area'],
|
||||
'annual_rent' => $data['annual_rent'],
|
||||
'status' => $data['status'],
|
||||
'note' => $data['note'],
|
||||
'updated_at' => $this->now(),
|
||||
);
|
||||
|
||||
$formats = array( '%s', '%d', '%f', '%f', '%s', '%s', '%s' );
|
||||
|
||||
if ( null === $payload['area'] ) {
|
||||
$payload['area'] = null;
|
||||
$formats[2] = '%s';
|
||||
}
|
||||
|
||||
if ( null === $payload['annual_rent'] ) {
|
||||
$payload['annual_rent'] = null;
|
||||
$formats[3] = '%s';
|
||||
}
|
||||
|
||||
if ( $id > 0 ) {
|
||||
$this->wpdb->update( $this->table, $payload, array( 'id' => $id ), $formats, array( '%d' ) );
|
||||
return $id;
|
||||
}
|
||||
|
||||
$payload['created_at'] = $this->now();
|
||||
$this->wpdb->insert( $this->table, $payload, array( '%s', '%d', $formats[2], $formats[3], '%s', '%s', '%s', '%s' ) );
|
||||
|
||||
return $this->wpdb->insert_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a parcel label already exists in the same section.
|
||||
*
|
||||
* @param string $label Parcel label.
|
||||
* @param int $section_id Section ID.
|
||||
* @param int $exclude_id Optional parcel ID.
|
||||
* @return bool
|
||||
*/
|
||||
public function label_exists( $label, $section_id, $exclude_id = 0 ) {
|
||||
$sql = "SELECT COUNT(*) FROM {$this->table} WHERE label = %s AND section_id = %d";
|
||||
$params = array( $label, $section_id );
|
||||
|
||||
if ( $exclude_id > 0 ) {
|
||||
$sql .= ' AND id != %d';
|
||||
$params[] = $exclude_id;
|
||||
}
|
||||
|
||||
return (int) $this->wpdb->get_var( $this->wpdb->prepare( $sql, $params ) ) > 0; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
}
|
||||
139
includes/Repositories/SectionRepository.php
Normal file
139
includes/Repositories/SectionRepository.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
/**
|
||||
* Section repository.
|
||||
*
|
||||
* @package KGV\VereinManager
|
||||
*/
|
||||
|
||||
namespace KGV\VereinManager\Repositories;
|
||||
|
||||
use KGV\VereinManager\Schema;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class SectionRepository extends AbstractRepository {
|
||||
|
||||
/**
|
||||
* Resolve table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function resolve_table() {
|
||||
return Schema::table( 'sections' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Search section list.
|
||||
*
|
||||
* @param array $args Query arguments.
|
||||
* @return array
|
||||
*/
|
||||
public function search( $args = array() ) {
|
||||
$search = isset( $args['s'] ) ? sanitize_text_field( wp_unslash( $args['s'] ) ) : '';
|
||||
$status = isset( $args['status'] ) ? sanitize_key( wp_unslash( $args['status'] ) ) : '';
|
||||
$orderby = $this->sanitize_orderby( isset( $args['orderby'] ) ? sanitize_key( wp_unslash( $args['orderby'] ) ) : 'name', array( 'name', 'status', 'created_at', 'updated_at' ), 'name' );
|
||||
$order = $this->sanitize_order( isset( $args['order'] ) ? sanitize_key( wp_unslash( $args['order'] ) ) : 'ASC' );
|
||||
|
||||
$sql = "SELECT * FROM {$this->table} WHERE 1=1";
|
||||
$params = array();
|
||||
|
||||
if ( '' !== $search ) {
|
||||
$like = '%' . $this->wpdb->esc_like( $search ) . '%';
|
||||
$sql .= ' AND (name LIKE %s OR description LIKE %s)';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
}
|
||||
|
||||
if ( in_array( $status, array( 'active', 'inactive' ), true ) ) {
|
||||
$sql .= ' AND status = %s';
|
||||
$params[] = $status;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY {$orderby} {$order}";
|
||||
|
||||
if ( ! empty( $params ) ) {
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
return $this->wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or update a section.
|
||||
*
|
||||
* @param array $data Section data.
|
||||
* @param int $id Optional ID.
|
||||
* @return int|false
|
||||
*/
|
||||
public function save( $data, $id = 0 ) {
|
||||
$payload = array(
|
||||
'name' => $data['name'],
|
||||
'description' => $data['description'],
|
||||
'status' => $data['status'],
|
||||
'updated_at' => $this->now(),
|
||||
);
|
||||
|
||||
$formats = array( '%s', '%s', '%s', '%s' );
|
||||
|
||||
if ( $id > 0 ) {
|
||||
$this->wpdb->update( $this->table, $payload, array( 'id' => $id ), $formats, array( '%d' ) );
|
||||
return $id;
|
||||
}
|
||||
|
||||
$payload['created_at'] = $this->now();
|
||||
$this->wpdb->insert( $this->table, $payload, array( '%s', '%s', '%s', '%s', '%s' ) );
|
||||
|
||||
return $this->wpdb->insert_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a section name already exists.
|
||||
*
|
||||
* @param string $name Section name.
|
||||
* @param int $exclude_id Optional ID to exclude.
|
||||
* @return bool
|
||||
*/
|
||||
public function name_exists( $name, $exclude_id = 0 ) {
|
||||
$sql = "SELECT COUNT(*) FROM {$this->table} WHERE name = %s";
|
||||
|
||||
if ( $exclude_id > 0 ) {
|
||||
$sql .= ' AND id != %d';
|
||||
return (int) $this->wpdb->get_var( $this->wpdb->prepare( $sql, $name, $exclude_id ) ) > 0; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
return (int) $this->wpdb->get_var( $this->wpdb->prepare( $sql, $name ) ) > 0; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight options list.
|
||||
*
|
||||
* @param bool $active_only Restrict to active sections.
|
||||
* @return array
|
||||
*/
|
||||
public function all_for_options( $active_only = false ) {
|
||||
$sql = "SELECT * FROM {$this->table}";
|
||||
|
||||
if ( $active_only ) {
|
||||
$sql .= " WHERE status = 'active'";
|
||||
}
|
||||
|
||||
$sql .= ' ORDER BY name ASC';
|
||||
|
||||
return $this->wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether section still has related records.
|
||||
*
|
||||
* @param int $id Section ID.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_in_use( $id ) {
|
||||
$parcel_count = (int) $this->wpdb->get_var( $this->wpdb->prepare( 'SELECT COUNT(*) FROM ' . Schema::table( 'parcels' ) . ' WHERE section_id = %d', $id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$meter_count = (int) $this->wpdb->get_var( $this->wpdb->prepare( 'SELECT COUNT(*) FROM ' . Schema::table( 'meters' ) . ' WHERE section_id = %d', $id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
|
||||
return $parcel_count > 0 || $meter_count > 0;
|
||||
}
|
||||
}
|
||||
112
includes/Repositories/TenantRepository.php
Normal file
112
includes/Repositories/TenantRepository.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/**
|
||||
* Tenant repository.
|
||||
*
|
||||
* @package KGV\VereinManager
|
||||
*/
|
||||
|
||||
namespace KGV\VereinManager\Repositories;
|
||||
|
||||
use KGV\VereinManager\Schema;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class TenantRepository extends AbstractRepository {
|
||||
|
||||
/**
|
||||
* Resolve table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function resolve_table() {
|
||||
return Schema::table( 'tenants' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Search tenant list.
|
||||
*
|
||||
* @param array $args Query arguments.
|
||||
* @return array
|
||||
*/
|
||||
public function search( $args = array() ) {
|
||||
$search = isset( $args['s'] ) ? sanitize_text_field( wp_unslash( $args['s'] ) ) : '';
|
||||
$status = isset( $args['status'] ) ? sanitize_key( wp_unslash( $args['status'] ) ) : '';
|
||||
$orderby = $this->sanitize_orderby( isset( $args['orderby'] ) ? sanitize_key( wp_unslash( $args['orderby'] ) ) : 'last_name', array( 'last_name', 'first_name', 'contract_start', 'is_active', 'created_at' ), 'last_name' );
|
||||
$order = $this->sanitize_order( isset( $args['order'] ) ? sanitize_key( wp_unslash( $args['order'] ) ) : 'ASC' );
|
||||
|
||||
$parcel_tenants = Schema::table( 'parcel_tenants' );
|
||||
|
||||
$sql = "SELECT t.*, (SELECT COUNT(*) FROM {$parcel_tenants} pt WHERE pt.tenant_id = t.id) AS parcel_count
|
||||
FROM {$this->table} t
|
||||
WHERE 1=1";
|
||||
|
||||
$params = array();
|
||||
|
||||
if ( '' !== $search ) {
|
||||
$like = '%' . $this->wpdb->esc_like( $search ) . '%';
|
||||
$sql .= ' AND (t.first_name LIKE %s OR t.last_name LIKE %s OR t.email LIKE %s OR t.phone LIKE %s)';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
}
|
||||
|
||||
if ( in_array( $status, array( 'active', 'inactive' ), true ) ) {
|
||||
$sql .= ' AND t.is_active = ' . ( 'active' === $status ? '1' : '0' );
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY t.{$orderby} {$order}, t.id DESC";
|
||||
|
||||
if ( ! empty( $params ) ) {
|
||||
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
return $this->wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or update tenant.
|
||||
*
|
||||
* @param array $data Tenant data.
|
||||
* @param int $id Optional ID.
|
||||
* @return int|false
|
||||
*/
|
||||
public function save( $data, $id = 0 ) {
|
||||
$payload = array(
|
||||
'first_name' => $data['first_name'],
|
||||
'last_name' => $data['last_name'],
|
||||
'address' => $data['address'],
|
||||
'phone' => $data['phone'],
|
||||
'email' => $data['email'],
|
||||
'contract_start' => $data['contract_start'],
|
||||
'contract_end' => $data['contract_end'] ? $data['contract_end'] : null,
|
||||
'is_active' => $data['is_active'],
|
||||
'note' => $data['note'],
|
||||
'updated_at' => $this->now(),
|
||||
);
|
||||
|
||||
$formats = array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s' );
|
||||
|
||||
if ( $id > 0 ) {
|
||||
$this->wpdb->update( $this->table, $payload, array( 'id' => $id ), $formats, array( '%d' ) );
|
||||
return $id;
|
||||
}
|
||||
|
||||
$payload['created_at'] = $this->now();
|
||||
$this->wpdb->insert( $this->table, $payload, array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s', '%s' ) );
|
||||
|
||||
return $this->wpdb->insert_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return active tenants for selection lists.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function all_active() {
|
||||
$sql = "SELECT * FROM {$this->table} WHERE is_active = 1 ORDER BY last_name ASC, first_name ASC";
|
||||
return $this->wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user