248 lines
7.2 KiB
PHP
248 lines
7.2 KiB
PHP
<?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 ) );
|
|
}
|
|
}
|