4054 lines
185 KiB
PHP
4054 lines
185 KiB
PHP
<?php
|
||
/**
|
||
* Backend administration UI.
|
||
*
|
||
* @package KGV\VereinManager
|
||
*/
|
||
|
||
namespace KGV\VereinManager\Admin;
|
||
|
||
use KGV\VereinManager\Roles;
|
||
use KGV\VereinManager\Validator;
|
||
use KGV\VereinManager\Repositories\AssignmentRepository;
|
||
use KGV\VereinManager\Repositories\ChatRepository;
|
||
use KGV\VereinManager\Repositories\CostRepository;
|
||
use KGV\VereinManager\Repositories\MeterReadingRepository;
|
||
use KGV\VereinManager\Repositories\MeterRepository;
|
||
use KGV\VereinManager\Repositories\ParcelRepository;
|
||
use KGV\VereinManager\Repositories\SectionRepository;
|
||
use KGV\VereinManager\Repositories\TenantRepository;
|
||
use KGV\VereinManager\Services\ParcelService;
|
||
|
||
if ( ! defined( 'ABSPATH' ) ) {
|
||
exit;
|
||
}
|
||
|
||
class Admin {
|
||
|
||
/**
|
||
* Validator instance.
|
||
*
|
||
* @var Validator
|
||
*/
|
||
private $validator;
|
||
|
||
/**
|
||
* Repositories and services.
|
||
*/
|
||
private $sections;
|
||
private $parcels;
|
||
private $meters;
|
||
private $readings;
|
||
private $tenants;
|
||
private $assignments;
|
||
private $chat;
|
||
private $costs;
|
||
private $parcel_service;
|
||
|
||
/**
|
||
* Construct admin controller.
|
||
*/
|
||
public function __construct() {
|
||
$this->validator = new Validator();
|
||
$this->sections = new SectionRepository();
|
||
$this->parcels = new ParcelRepository();
|
||
$this->meters = new MeterRepository();
|
||
$this->readings = new MeterReadingRepository();
|
||
$this->tenants = new TenantRepository();
|
||
$this->assignments = new AssignmentRepository();
|
||
$this->chat = new ChatRepository();
|
||
$this->costs = new CostRepository();
|
||
$this->parcel_service = new ParcelService();
|
||
}
|
||
|
||
/**
|
||
* Register WP hooks.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function register_hooks() {
|
||
add_action( 'admin_menu', array( $this, 'register_menus' ) );
|
||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
|
||
add_action( 'admin_init', array( $this, 'handle_requests' ) );
|
||
add_action( 'wp_ajax_kgvvm_fetch_chat_messages', array( $this, 'ajax_fetch_chat_messages' ) );
|
||
add_action( 'wp_ajax_kgvvm_send_chat_message', array( $this, 'ajax_send_chat_message' ) );
|
||
}
|
||
|
||
/**
|
||
* Register admin pages.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function register_menus() {
|
||
$cap = 'manage_kleingarten';
|
||
|
||
if ( current_user_can( $cap ) ) {
|
||
add_menu_page(
|
||
__( 'Kleingartenverwaltung', KGVVM_TEXT_DOMAIN ),
|
||
__( 'Kleingarten', KGVVM_TEXT_DOMAIN ),
|
||
$cap,
|
||
'kgvvm-dashboard',
|
||
array( $this, 'render_dashboard' ),
|
||
'dashicons-admin-multisite',
|
||
24
|
||
);
|
||
|
||
add_submenu_page( 'kgvvm-dashboard', __( 'Dashboard', KGVVM_TEXT_DOMAIN ), __( 'Dashboard', KGVVM_TEXT_DOMAIN ), $cap, 'kgvvm-dashboard', array( $this, 'render_dashboard' ) );
|
||
add_submenu_page( 'kgvvm-dashboard', __( 'Meine Parzellen', KGVVM_TEXT_DOMAIN ), __( 'Meine Parzellen', KGVVM_TEXT_DOMAIN ), 'view_assigned_parcels', 'kgvvm-my-parcels', array( $this, 'render_my_parcels_page' ) );
|
||
add_submenu_page( 'kgvvm-dashboard', __( 'Vereinschat', KGVVM_TEXT_DOMAIN ), __( 'Vereinschat', KGVVM_TEXT_DOMAIN ), 'view_assigned_parcels', 'kgvvm-chat', array( $this, 'render_chat_page' ) );
|
||
add_submenu_page( 'kgvvm-dashboard', __( 'Sparten', KGVVM_TEXT_DOMAIN ), __( 'Sparten', KGVVM_TEXT_DOMAIN ), 'edit_sparten', 'kgvvm-sparten', array( $this, 'render_sections_page' ) );
|
||
add_submenu_page( 'kgvvm-dashboard', __( 'Parzellen', KGVVM_TEXT_DOMAIN ), __( 'Parzellen', KGVVM_TEXT_DOMAIN ), 'edit_parzellen', 'kgvvm-parzellen', array( $this, 'render_parcels_page' ) );
|
||
add_submenu_page( 'kgvvm-dashboard', __( 'Zähler', KGVVM_TEXT_DOMAIN ), __( 'Zähler', KGVVM_TEXT_DOMAIN ), 'edit_zaehler', 'kgvvm-zaehler', array( $this, 'render_meters_page' ) );
|
||
add_submenu_page( 'kgvvm-dashboard', __( 'Verbrauch', KGVVM_TEXT_DOMAIN ), __( 'Verbrauch', KGVVM_TEXT_DOMAIN ), $cap, 'kgvvm-consumption', array( $this, 'render_consumption_page' ) );
|
||
add_submenu_page( 'kgvvm-dashboard', __( 'Kosten', KGVVM_TEXT_DOMAIN ), __( 'Kosten', KGVVM_TEXT_DOMAIN ), $cap, 'kgvvm-costs', array( $this, 'render_costs_page' ) );
|
||
add_submenu_page( 'kgvvm-dashboard', __( 'Pächter', KGVVM_TEXT_DOMAIN ), __( 'Pächter', KGVVM_TEXT_DOMAIN ), 'edit_paechter', 'kgvvm-paechter', array( $this, 'render_tenants_page' ) );
|
||
add_submenu_page( 'kgvvm-dashboard', __( 'Einstellungen', KGVVM_TEXT_DOMAIN ), __( 'Einstellungen', KGVVM_TEXT_DOMAIN ), Roles::SETTINGS_CAP, 'kgvvm-settings', array( $this, 'render_settings_page' ) );
|
||
}
|
||
|
||
if ( ! current_user_can( $cap ) && current_user_can( 'view_assigned_parcels' ) ) {
|
||
add_menu_page(
|
||
__( 'Meine Parzellen', KGVVM_TEXT_DOMAIN ),
|
||
__( 'Meine Parzellen', KGVVM_TEXT_DOMAIN ),
|
||
'view_assigned_parcels',
|
||
'kgvvm-my-parcels',
|
||
array( $this, 'render_my_parcels_page' ),
|
||
'dashicons-location-alt',
|
||
24
|
||
);
|
||
|
||
add_menu_page(
|
||
__( 'Vereinschat', KGVVM_TEXT_DOMAIN ),
|
||
__( 'Vereinschat', KGVVM_TEXT_DOMAIN ),
|
||
'view_assigned_parcels',
|
||
'kgvvm-chat',
|
||
array( $this, 'render_chat_page' ),
|
||
'dashicons-format-chat',
|
||
25
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Load admin stylesheet on plugin pages.
|
||
*
|
||
* @param string $hook Current admin hook.
|
||
* @return void
|
||
*/
|
||
public function enqueue_assets( $hook ) {
|
||
if ( false === strpos( (string) $hook, 'kgvvm' ) ) {
|
||
return;
|
||
}
|
||
|
||
wp_enqueue_style( 'kgvvm-admin', KGVVM_PLUGIN_URL . 'assets/css/admin.css', array(), KGVVM_VERSION );
|
||
|
||
if ( false !== strpos( (string) $hook, 'kgvvm-chat' ) ) {
|
||
wp_enqueue_script( 'kgvvm-chat', KGVVM_PLUGIN_URL . 'assets/js/chat.js', array(), KGVVM_VERSION, true );
|
||
wp_localize_script(
|
||
'kgvvm-chat',
|
||
'kgvvmChatConfig',
|
||
array(
|
||
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||
'nonce' => wp_create_nonce( 'kgvvm_chat_nonce' ),
|
||
'refreshInterval' => 7000,
|
||
'i18n' => array(
|
||
'empty' => __( 'Noch keine Nachrichten in diesem Raum.', KGVVM_TEXT_DOMAIN ),
|
||
'loading' => __( 'Nachrichten werden geladen …', KGVVM_TEXT_DOMAIN ),
|
||
'sending' => __( 'Nachricht wird gesendet …', KGVVM_TEXT_DOMAIN ),
|
||
'fetchError' => __( 'Der Chat konnte gerade nicht aktualisiert werden.', KGVVM_TEXT_DOMAIN ),
|
||
'sendError' => __( 'Die Nachricht konnte nicht gesendet werden.', KGVVM_TEXT_DOMAIN ),
|
||
),
|
||
)
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Handle save/delete requests.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function handle_requests() {
|
||
if ( ! is_admin() || ( ! current_user_can( 'manage_kleingarten' ) && ! current_user_can( Roles::SETTINGS_CAP ) && ! current_user_can( 'submit_meter_readings' ) ) ) {
|
||
return;
|
||
}
|
||
|
||
$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
|
||
$view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : '';
|
||
$output = isset( $_GET['output'] ) ? sanitize_key( wp_unslash( $_GET['output'] ) ) : '';
|
||
|
||
if ( current_user_can( 'manage_kleingarten' ) && 'kgvvm-costs' === $page && 'statement' === $view && 'pdf' === $output ) {
|
||
$this->render_cost_statement_page();
|
||
return;
|
||
}
|
||
|
||
if ( 'POST' === strtoupper( isset( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '' ) && ! empty( $_POST['kgvvm_action'] ) ) {
|
||
$this->handle_post_action();
|
||
}
|
||
|
||
if ( ! empty( $_GET['kgvvm_action'] ) ) {
|
||
$this->handle_get_action();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Dispatch POST actions.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function handle_post_action() {
|
||
$action = sanitize_key( wp_unslash( $_POST['kgvvm_action'] ) );
|
||
|
||
switch ( $action ) {
|
||
case 'save_section':
|
||
$this->save_section();
|
||
break;
|
||
case 'save_parcel':
|
||
$this->save_parcel();
|
||
break;
|
||
case 'save_meter':
|
||
$this->save_meter();
|
||
break;
|
||
case 'swap_meter':
|
||
$this->swap_meter();
|
||
break;
|
||
case 'save_tenant':
|
||
$this->save_tenant();
|
||
break;
|
||
case 'save_cost_year':
|
||
$this->save_cost_year();
|
||
break;
|
||
case 'save_cost_prices':
|
||
$this->save_cost_prices();
|
||
break;
|
||
case 'save_cost':
|
||
$this->save_cost();
|
||
break;
|
||
case 'save_meter_reading':
|
||
$this->save_meter_reading();
|
||
break;
|
||
case 'save_settings':
|
||
$this->save_settings();
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Dispatch delete actions.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function handle_get_action() {
|
||
$action = sanitize_key( wp_unslash( $_GET['kgvvm_action'] ) );
|
||
$id = absint( isset( $_GET['id'] ) ? $_GET['id'] : 0 );
|
||
$nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
|
||
|
||
switch ( $action ) {
|
||
case 'delete_section':
|
||
$this->require_cap( 'edit_sparten' );
|
||
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_section_' . $id ) ) {
|
||
$this->redirect_with_notice( 'kgvvm-sparten', 'error', __( 'Der Löschvorgang für die Sparte wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
if ( $this->sections->is_in_use( $id ) ) {
|
||
$this->redirect_with_notice( 'kgvvm-sparten', 'error', __( 'Diese Sparte ist noch mit Parzellen oder Zählern verknüpft und kann nicht gelöscht werden.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
$this->sections->delete( $id );
|
||
$this->redirect_with_notice( 'kgvvm-sparten', 'success', __( 'Sparte wurde gelöscht.', KGVVM_TEXT_DOMAIN ) );
|
||
break;
|
||
case 'delete_parcel':
|
||
$this->require_cap( 'edit_parzellen' );
|
||
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_parcel_' . $id ) ) {
|
||
$this->redirect_with_notice( 'kgvvm-parzellen', 'error', __( 'Der Löschvorgang für die Parzelle wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
$this->parcel_service->delete( $id );
|
||
$this->redirect_with_notice( 'kgvvm-parzellen', 'success', __( 'Parzelle wurde gelöscht.', KGVVM_TEXT_DOMAIN ) );
|
||
break;
|
||
case 'delete_meter':
|
||
$this->require_cap( 'edit_zaehler' );
|
||
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_meter_' . $id ) ) {
|
||
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', __( 'Der Löschvorgang für den Zähler wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
if ( $this->meters->is_assigned( $id ) ) {
|
||
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', __( 'Ein zugeordneter Zähler kann nicht gelöscht werden. Bitte zuerst die Parzellenzuordnung entfernen.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
$this->meters->delete( $id );
|
||
$this->redirect_with_notice( 'kgvvm-zaehler', 'success', __( 'Zähler wurde gelöscht.', KGVVM_TEXT_DOMAIN ) );
|
||
break;
|
||
case 'delete_tenant':
|
||
$this->require_cap( 'edit_paechter' );
|
||
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_tenant_' . $id ) ) {
|
||
$this->redirect_with_notice( 'kgvvm-paechter', 'error', __( 'Der Löschvorgang für den Pächter wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
$this->assignments->purge_tenant( $id );
|
||
$this->tenants->delete( $id );
|
||
$this->redirect_with_notice( 'kgvvm-paechter', 'success', __( 'Pächter wurde gelöscht.', KGVVM_TEXT_DOMAIN ) );
|
||
break;
|
||
case 'delete_cost':
|
||
$this->require_cap( 'manage_kleingarten' );
|
||
$year = absint( isset( $_GET['year'] ) ? $_GET['year'] : current_time( 'Y' ) );
|
||
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_cost_' . $id ) ) {
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Der Löschvorgang für den Kostenposten wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
|
||
}
|
||
$cost = $this->costs->find( $id );
|
||
if ( ! $cost ) {
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Der gewünschte Kostenposten wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
|
||
}
|
||
$year = (int) $cost->entry_year;
|
||
$this->costs->delete( $id );
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'success', __( 'Kostenposten wurde gelöscht.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
|
||
break;
|
||
case 'export_readings_csv':
|
||
$this->export_readings_csv();
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Save section.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function save_section() {
|
||
$this->require_cap( 'edit_sparten' );
|
||
check_admin_referer( 'kgvvm_save_section' );
|
||
|
||
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
|
||
$data = $this->validator->sanitize_section( $_POST );
|
||
$errors = $this->validator->validate_section( $data );
|
||
|
||
if ( $this->sections->name_exists( $data['name'], $id ) ) {
|
||
$errors->add( 'duplicate_name', __( 'Es existiert bereits eine Sparte mit diesem Namen.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
if ( $errors->has_errors() ) {
|
||
$this->redirect_with_notice( 'kgvvm-sparten', 'error', $errors->get_error_message(), array( 'view' => 'form', 'id' => $id ) );
|
||
}
|
||
|
||
$section_id = $this->sections->save( $data, $id );
|
||
$main_ids = isset( $_POST['main_meter_ids'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['main_meter_ids'] ) ) : array();
|
||
|
||
$this->meters->sync_main_meters_for_section( $section_id, $main_ids );
|
||
$this->redirect_with_notice( 'kgvvm-sparten', 'success', __( 'Sparte wurde gespeichert.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
/**
|
||
* Save parcel.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function save_parcel() {
|
||
$this->require_cap( 'edit_parzellen' );
|
||
check_admin_referer( 'kgvvm_save_parcel' );
|
||
|
||
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
|
||
$result = $this->parcel_service->save( $id, $_POST );
|
||
|
||
if ( is_wp_error( $result ) ) {
|
||
$this->redirect_with_notice( 'kgvvm-parzellen', 'error', $result->get_error_message(), array( 'view' => 'form', 'id' => $id ) );
|
||
}
|
||
|
||
$this->redirect_with_notice( 'kgvvm-parzellen', 'success', __( 'Parzelle wurde gespeichert.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
/**
|
||
* Save meter.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function save_meter() {
|
||
$this->require_cap( 'edit_zaehler' );
|
||
check_admin_referer( 'kgvvm_save_meter' );
|
||
|
||
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
|
||
$data = $this->validator->sanitize_meter( $_POST );
|
||
$errors = $this->validator->validate_meter( $data );
|
||
|
||
if ( $this->meters->meter_number_exists( $data['meter_number'], $data['type'], $id ) ) {
|
||
$errors->add( 'duplicate_meter_number', __( 'Diese Zählernummer existiert für den gewählten Typ bereits.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
if ( $errors->has_errors() ) {
|
||
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', $errors->get_error_message(), array( 'view' => 'form', 'id' => $id ) );
|
||
}
|
||
|
||
$this->meters->save( $data, $id );
|
||
$this->redirect_with_notice( 'kgvvm-zaehler', 'success', __( 'Zähler wurde gespeichert.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
/**
|
||
* Swap a meter and archive the old one.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function swap_meter() {
|
||
$this->require_cap( 'edit_zaehler' );
|
||
check_admin_referer( 'kgvvm_swap_meter' );
|
||
|
||
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
|
||
$old_meter = $id ? $this->meters->find( $id ) : null;
|
||
|
||
if ( ! $old_meter ) {
|
||
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', __( 'Der gewünschte Zähler wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
$swap_note = sanitize_textarea_field( wp_unslash( isset( $_POST['swap_note'] ) ? $_POST['swap_note'] : '' ) );
|
||
$new_data = $this->validator->sanitize_meter(
|
||
array(
|
||
'type' => $old_meter->type,
|
||
'meter_number' => isset( $_POST['new_meter_number'] ) ? $_POST['new_meter_number'] : '',
|
||
'section_id' => $old_meter->section_id,
|
||
'installed_at' => isset( $_POST['swap_date'] ) ? $_POST['swap_date'] : '',
|
||
'calibration_year' => isset( $_POST['new_calibration_year'] ) ? $_POST['new_calibration_year'] : '',
|
||
'is_main_meter' => ! empty( $old_meter->is_main_meter ) ? 1 : 0,
|
||
'is_active' => 1,
|
||
'note' => $swap_note,
|
||
)
|
||
);
|
||
$errors = $this->validator->validate_meter( $new_data );
|
||
|
||
if ( $this->meters->meter_number_exists( $new_data['meter_number'], $new_data['type'] ) ) {
|
||
$errors->add( 'duplicate_meter_number', __( 'Die neue Zählernummer existiert für diesen Typ bereits.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
if ( '' === $new_data['installed_at'] ) {
|
||
$errors->add( 'swap_date_required', __( 'Bitte ein Tauschdatum angeben.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
$parcel_id = (int) $old_meter->parcel_id;
|
||
$latest = $this->readings->get_latest_for_meter( $old_meter->id );
|
||
$old_final_raw = isset( $_POST['old_final_reading'] ) ? str_replace( ',', '.', wp_unslash( $_POST['old_final_reading'] ) ) : '';
|
||
$new_initial_raw = isset( $_POST['new_initial_reading'] ) ? str_replace( ',', '.', wp_unslash( $_POST['new_initial_reading'] ) ) : '';
|
||
$old_final_reading = '' === trim( (string) $old_final_raw ) ? '' : (float) $old_final_raw;
|
||
$new_initial_reading = '' === trim( (string) $new_initial_raw ) ? '' : (float) $new_initial_raw;
|
||
|
||
if ( '' !== trim( (string) $old_final_raw ) ) {
|
||
if ( ! is_numeric( $old_final_raw ) ) {
|
||
$errors->add( 'old_final_invalid', __( 'Bitte einen gültigen Endstand für den alten Zähler eingeben.', KGVVM_TEXT_DOMAIN ) );
|
||
} elseif ( (float) $old_final_reading < 0 ) {
|
||
$errors->add( 'old_final_negative', __( 'Der Endstand des alten Zählers darf nicht negativ sein.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
}
|
||
|
||
if ( $parcel_id > 0 ) {
|
||
if ( '' === trim( (string) $new_initial_raw ) ) {
|
||
$errors->add( 'new_initial_required', __( 'Bitte einen Startstand für den neuen Zähler eingeben.', KGVVM_TEXT_DOMAIN ) );
|
||
} elseif ( ! is_numeric( $new_initial_raw ) ) {
|
||
$errors->add( 'new_initial_invalid', __( 'Bitte einen gültigen Startstand für den neuen Zähler eingeben.', KGVVM_TEXT_DOMAIN ) );
|
||
} elseif ( (float) $new_initial_reading < 0 ) {
|
||
$errors->add( 'new_initial_negative', __( 'Der Startstand des neuen Zählers darf nicht negativ sein.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
}
|
||
|
||
if ( $latest && $new_data['installed_at'] && strtotime( $new_data['installed_at'] ) < strtotime( $latest->reading_date ) ) {
|
||
$errors->add( 'swap_date_older', __( 'Das Tauschdatum darf nicht vor der letzten gespeicherten Ablesung liegen.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
if ( $latest && '' !== $old_final_reading && (float) $old_final_reading < (float) $latest->reading_value ) {
|
||
$errors->add( 'swap_final_low', __( 'Der Endstand des alten Zählers darf nicht kleiner als die letzte Ablesung sein.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
if ( $errors->has_errors() ) {
|
||
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', $errors->get_error_message(), array( 'id' => $id, 'open_swap' => 1 ) );
|
||
}
|
||
|
||
$swap_label = wp_date( 'd.m.Y', strtotime( $new_data['installed_at'] ) );
|
||
$new_data['note'] = trim(
|
||
implode(
|
||
"\n\n",
|
||
array_filter(
|
||
array(
|
||
sprintf( __( 'Ersatzzähler für %1$s seit %2$s.', KGVVM_TEXT_DOMAIN ), $old_meter->meter_number, $swap_label ),
|
||
$swap_note,
|
||
)
|
||
)
|
||
)
|
||
);
|
||
|
||
$new_meter_id = $this->meters->save( $new_data, 0 );
|
||
|
||
if ( ! $new_meter_id ) {
|
||
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', __( 'Der neue Zähler konnte nicht angelegt werden.', KGVVM_TEXT_DOMAIN ), array( 'id' => $id, 'open_swap' => 1 ) );
|
||
}
|
||
|
||
if ( $parcel_id > 0 && '' !== $old_final_reading ) {
|
||
$this->readings->save(
|
||
array(
|
||
'meter_id' => $old_meter->id,
|
||
'parcel_id' => $parcel_id,
|
||
'reading_value' => $old_final_reading,
|
||
'reading_date' => $new_data['installed_at'],
|
||
'note' => __( 'Abschlussstand beim Zählerwechsel.', KGVVM_TEXT_DOMAIN ),
|
||
'submitted_by' => get_current_user_id(),
|
||
)
|
||
);
|
||
}
|
||
|
||
$this->meters->release_meter( $old_meter->id );
|
||
$this->meters->save(
|
||
array(
|
||
'type' => $old_meter->type,
|
||
'meter_number' => $old_meter->meter_number,
|
||
'section_id' => $old_meter->section_id,
|
||
'installed_at' => $old_meter->installed_at,
|
||
'calibration_year' => ! empty( $old_meter->calibration_year ) ? (int) $old_meter->calibration_year : null,
|
||
'is_active' => 0,
|
||
'note' => trim(
|
||
implode(
|
||
"\n\n",
|
||
array_filter(
|
||
array(
|
||
$old_meter->note,
|
||
sprintf( __( 'Zählerwechsel am %1$s. Neuer Zähler: %2$s.', KGVVM_TEXT_DOMAIN ), $swap_label, $new_data['meter_number'] ),
|
||
$swap_note ? sprintf( __( 'Tauschhinweis: %s', KGVVM_TEXT_DOMAIN ), $swap_note ) : '',
|
||
)
|
||
)
|
||
)
|
||
),
|
||
),
|
||
$old_meter->id
|
||
);
|
||
|
||
if ( $parcel_id > 0 ) {
|
||
$this->meters->assign_to_parcel( $new_meter_id, $parcel_id );
|
||
$this->readings->save(
|
||
array(
|
||
'meter_id' => $new_meter_id,
|
||
'parcel_id' => $parcel_id,
|
||
'reading_value' => $new_initial_reading,
|
||
'reading_date' => $new_data['installed_at'],
|
||
'note' => __( 'Startstand nach Zählerwechsel.', KGVVM_TEXT_DOMAIN ),
|
||
'submitted_by' => get_current_user_id(),
|
||
)
|
||
);
|
||
}
|
||
|
||
$this->redirect_with_notice( 'kgvvm-zaehler', 'success', __( 'Der Zähler wurde erfolgreich getauscht.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
/**
|
||
* Save tenant.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function save_tenant() {
|
||
$this->require_cap( 'edit_paechter' );
|
||
check_admin_referer( 'kgvvm_save_tenant' );
|
||
|
||
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
|
||
$data = $this->validator->sanitize_tenant( $_POST );
|
||
$errors = $this->validator->validate_tenant( $data );
|
||
|
||
if ( $errors->has_errors() ) {
|
||
$this->redirect_with_notice( 'kgvvm-paechter', 'error', $errors->get_error_message(), array( 'view' => 'form', 'id' => $id ) );
|
||
}
|
||
|
||
$this->tenants->save( $data, $id );
|
||
$this->redirect_with_notice( 'kgvvm-paechter', 'success', __( 'Pächter wurde gespeichert.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
/**
|
||
* Save a meter reading from a member or administrator.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function save_meter_reading() {
|
||
if ( ! current_user_can( 'submit_meter_readings' ) && ! current_user_can( 'manage_kleingarten' ) ) {
|
||
wp_die( esc_html__( 'Sie haben keine Berechtigung für diese Aktion.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
check_admin_referer( 'kgvvm_save_meter_reading' );
|
||
|
||
$return_page = isset( $_POST['return_page'] ) ? sanitize_key( wp_unslash( $_POST['return_page'] ) ) : 'kgvvm-my-parcels';
|
||
$return_view = isset( $_POST['return_view'] ) ? sanitize_key( wp_unslash( $_POST['return_view'] ) ) : '';
|
||
$return_id = absint( isset( $_POST['return_id'] ) ? $_POST['return_id'] : 0 );
|
||
$return_args = array();
|
||
|
||
if ( ! in_array( $return_page, array( 'kgvvm-my-parcels', 'kgvvm-sparten', 'kgvvm-zaehler', 'kgvvm-consumption' ), true ) ) {
|
||
$return_page = 'kgvvm-my-parcels';
|
||
}
|
||
|
||
if ( $return_view ) {
|
||
$return_args['view'] = $return_view;
|
||
}
|
||
|
||
if ( $return_id > 0 ) {
|
||
$return_args['id'] = $return_id;
|
||
}
|
||
|
||
$data = $this->validator->sanitize_meter_reading( $_POST );
|
||
$errors = $this->validator->validate_meter_reading( $data );
|
||
$meter = $this->meters->find( $data['meter_id'] );
|
||
|
||
if ( ! current_user_can( 'manage_kleingarten' ) && ! $this->assignments->user_has_parcel( get_current_user_id(), $data['parcel_id'] ) ) {
|
||
$errors->add( 'forbidden_parcel', __( 'Sie dürfen nur Ablesungen für Ihre zugewiesenen Parzellen erfassen.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
if ( ! $meter || (int) $meter->parcel_id !== (int) $data['parcel_id'] ) {
|
||
$errors->add( 'invalid_meter_link', __( 'Der ausgewählte Zähler ist dieser Parzelle nicht zugeordnet.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
$latest = $meter ? $this->readings->get_latest_for_meter( $data['meter_id'] ) : null;
|
||
|
||
if ( $latest && $data['reading_date'] && strtotime( $data['reading_date'] ) < strtotime( $latest->reading_date ) ) {
|
||
$errors->add( 'reading_date_older', __( 'Das Ablesedatum darf nicht vor der letzten gespeicherten Ablesung liegen.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
if ( $latest && (float) $data['reading_value'] < (float) $latest->reading_value ) {
|
||
$errors->add( 'reading_too_low', __( 'Der neue Zählerstand darf nicht kleiner als die letzte erfasste Ablesung sein.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
if ( $errors->has_errors() ) {
|
||
$this->redirect_with_notice( $return_page, 'error', $errors->get_error_message(), $return_args );
|
||
}
|
||
|
||
$data['submitted_by'] = get_current_user_id();
|
||
$data['is_self_reading'] = ( ! current_user_can( 'manage_kleingarten' ) && current_user_can( 'submit_meter_readings' ) ) ? 1 : 0;
|
||
$warning_message = $this->build_meter_jump_warning_message( $meter, $latest, (float) $data['reading_value'] );
|
||
$this->readings->save( $data );
|
||
|
||
if ( $warning_message ) {
|
||
$this->redirect_with_notice( $return_page, 'warning', sprintf( __( 'Die Ablesung wurde gespeichert. %s', KGVVM_TEXT_DOMAIN ), $warning_message ), $return_args );
|
||
}
|
||
|
||
$this->redirect_with_notice( $return_page, 'success', __( 'Die Ablesung wurde gespeichert.', KGVVM_TEXT_DOMAIN ), $return_args );
|
||
}
|
||
|
||
/**
|
||
* Save general settings.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function save_settings() {
|
||
$this->require_cap( Roles::SETTINGS_CAP );
|
||
check_admin_referer( 'kgvvm_save_settings' );
|
||
|
||
$current_settings = wp_parse_args( get_option( 'kgvvm_settings', array() ), $this->get_settings_defaults() );
|
||
$water_threshold = isset( $_POST['water_usage_alert_threshold'] ) ? (float) str_replace( ',', '.', wp_unslash( $_POST['water_usage_alert_threshold'] ) ) : 25;
|
||
$power_threshold = isset( $_POST['power_usage_alert_threshold'] ) ? (float) str_replace( ',', '.', wp_unslash( $_POST['power_usage_alert_threshold'] ) ) : 1000;
|
||
$power_unit = isset( $_POST['power_unit'] ) ? sanitize_key( wp_unslash( $_POST['power_unit'] ) ) : 'kwh';
|
||
$power_unit = in_array( $power_unit, array( 'kwh', 'mwh' ), true ) ? $power_unit : 'kwh';
|
||
$pdf_club_name = isset( $_POST['pdf_club_name'] ) ? sanitize_text_field( wp_unslash( $_POST['pdf_club_name'] ) ) : $current_settings['pdf_club_name'];
|
||
$pdf_logo_url = isset( $_POST['pdf_logo_url'] ) ? esc_url_raw( trim( wp_unslash( $_POST['pdf_logo_url'] ) ) ) : '';
|
||
$pdf_contact = isset( $_POST['pdf_contact_block'] ) ? sanitize_textarea_field( wp_unslash( $_POST['pdf_contact_block'] ) ) : '';
|
||
$pdf_intro_text = isset( $_POST['pdf_intro_text'] ) ? sanitize_textarea_field( wp_unslash( $_POST['pdf_intro_text'] ) ) : '';
|
||
$pdf_footer_text = isset( $_POST['pdf_footer_text'] ) ? sanitize_textarea_field( wp_unslash( $_POST['pdf_footer_text'] ) ) : '';
|
||
|
||
update_option(
|
||
'kgvvm_settings',
|
||
array_merge(
|
||
$current_settings,
|
||
array(
|
||
'allow_multiple_member_parcels' => ! empty( $_POST['allow_multiple_member_parcels'] ) ? 1 : 0,
|
||
'water_usage_alert_threshold' => max( 0, $water_threshold ),
|
||
'power_usage_alert_threshold' => max( 0, $power_threshold ),
|
||
'power_unit' => $power_unit,
|
||
'pdf_club_name' => $pdf_club_name,
|
||
'pdf_logo_url' => $pdf_logo_url,
|
||
'pdf_contact_block' => $pdf_contact,
|
||
'pdf_intro_text' => $pdf_intro_text,
|
||
'pdf_footer_text' => $pdf_footer_text,
|
||
)
|
||
),
|
||
false
|
||
);
|
||
|
||
$this->redirect_with_notice( 'kgvvm-settings', 'success', __( 'Einstellungen wurden gespeichert.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
/**
|
||
* Persist one year for the cost dropdown.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function save_cost_year() {
|
||
$this->require_cap( 'manage_kleingarten' );
|
||
check_admin_referer( 'kgvvm_save_cost_year' );
|
||
|
||
$entry_year = $this->validator->sanitize_cost_year( $_POST );
|
||
$errors = $this->validator->validate_cost_year( $entry_year );
|
||
|
||
if ( $errors->has_errors() ) {
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'error', $errors->get_error_message(), array( 'year' => current_time( 'Y' ) ) );
|
||
}
|
||
|
||
if ( ! $this->costs->save_year( $entry_year ) ) {
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Das Jahr konnte nicht gespeichert werden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $entry_year ) );
|
||
}
|
||
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'success', __( 'Das Jahr wurde zur Auswahlliste hinzugefügt.', KGVVM_TEXT_DOMAIN ), array( 'year' => $entry_year ) );
|
||
}
|
||
|
||
/**
|
||
* Save section-specific yearly prices.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function save_cost_prices() {
|
||
$this->require_cap( 'manage_kleingarten' );
|
||
check_admin_referer( 'kgvvm_save_cost_prices' );
|
||
|
||
$data = $this->validator->sanitize_cost_year_settings( $_POST );
|
||
$errors = $this->validator->validate_cost_year_settings( $data );
|
||
|
||
if ( $errors->has_errors() ) {
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'error', $errors->get_error_message(), array( 'year' => $data['entry_year'] ) );
|
||
}
|
||
|
||
if ( ! $this->costs->save_section_prices( $data ) ) {
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Die Preise für diese Sparte konnten nicht gespeichert werden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $data['entry_year'] ) );
|
||
}
|
||
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'success', __( 'Die Preise pro Sparte wurden gespeichert.', KGVVM_TEXT_DOMAIN ), array( 'year' => $data['entry_year'] ) );
|
||
}
|
||
|
||
/**
|
||
* Save one annual cost entry.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function save_cost() {
|
||
$this->require_cap( 'manage_kleingarten' );
|
||
check_admin_referer( 'kgvvm_save_cost' );
|
||
|
||
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
|
||
$data = $this->validator->sanitize_cost_entry( $_POST );
|
||
$errors = $this->validator->validate_cost_entry( $data );
|
||
|
||
if ( $errors->has_errors() ) {
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'error', $errors->get_error_message(), array( 'year' => $data['entry_year'], 'id' => $id ) );
|
||
}
|
||
|
||
$active_parcels = array_values(
|
||
array_filter(
|
||
$this->parcels->search(),
|
||
static function( $parcel ) {
|
||
return isset( $parcel->status ) && 'inactive' !== $parcel->status;
|
||
}
|
||
)
|
||
);
|
||
$member_count = count( $this->assignments->get_member_users() );
|
||
$multiplier = 'member' === $data['distribution_type'] ? $member_count : count( $active_parcels );
|
||
$data['total_cost'] = (float) $data['unit_amount'] * max( 0, $multiplier );
|
||
|
||
$saved = $this->costs->save( $data, $id );
|
||
|
||
if ( ! $saved ) {
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Der Kostenposten konnte nicht gespeichert werden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $data['entry_year'], 'id' => $id ) );
|
||
}
|
||
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'success', __( 'Kostenposten wurde gespeichert.', KGVVM_TEXT_DOMAIN ), array( 'year' => $data['entry_year'] ) );
|
||
}
|
||
|
||
/**
|
||
* Render dashboard.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function render_dashboard() {
|
||
$this->require_cap( 'manage_kleingarten' );
|
||
|
||
$sections_total = count( $this->sections->search() );
|
||
$parcels_total = count( $this->parcels->search() );
|
||
$meters_free = count( $this->meters->search( array( 'assignment' => 'free' ) ) );
|
||
$meters_used = count( $this->meters->search( array( 'assignment' => 'assigned' ) ) );
|
||
$tenants_total = count( $this->tenants->search() );
|
||
$members_total = count( $this->assignments->get_member_users() );
|
||
?>
|
||
<div class='wrap'>
|
||
<h1><?php echo esc_html__( 'Kleingartenverwaltung', KGVVM_TEXT_DOMAIN ); ?></h1>
|
||
<?php $this->render_notice(); ?>
|
||
|
||
<div class='kgvvm-grid'>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $sections_total ); ?></h2>
|
||
<p><?php echo esc_html__( 'Sparten', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $parcels_total ); ?></h2>
|
||
<p><?php echo esc_html__( 'Parzellen', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $meters_free ); ?></h2>
|
||
<p><?php echo esc_html__( 'Freie Zähler', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $meters_used ); ?></h2>
|
||
<p><?php echo esc_html__( 'Zugeordnete Zähler', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $tenants_total ); ?></h2>
|
||
<p><?php echo esc_html__( 'Pächter', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $members_total ); ?></h2>
|
||
<p><?php echo esc_html__( 'WordPress-Mitglieder', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html__( 'Fachmodell', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<p><?php echo esc_html__( 'Das Plugin verwendet eigene Datenbanktabellen statt Custom Post Types, weil das Fachmodell stark relational ist: Sparten, Parzellen, Zähler, Pächter und Zuordnungen müssen performant gefiltert, eindeutig verknüpft und später um Zählerstände, Verträge oder Umlagen erweitert werden können.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render consumption report page.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function render_consumption_page() {
|
||
$this->require_cap( 'manage_kleingarten' );
|
||
|
||
$section_id = isset( $_GET['section_id'] ) ? absint( $_GET['section_id'] ) : 0;
|
||
$date_from = isset( $_GET['date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['date_from'] ) ) : '';
|
||
$date_to = isset( $_GET['date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['date_to'] ) ) : '';
|
||
$order = isset( $_GET['order'] ) ? strtoupper( sanitize_key( wp_unslash( $_GET['order'] ) ) ) : 'DESC';
|
||
$order = 'ASC' === $order ? 'ASC' : 'DESC';
|
||
$sections = $this->sections->all_for_options();
|
||
|
||
if ( $date_from && $date_to && strtotime( $date_from ) > strtotime( $date_to ) ) {
|
||
$tmp = $date_from;
|
||
$date_from = $date_to;
|
||
$date_to = $tmp;
|
||
}
|
||
|
||
$rows = $this->readings->get_consumption_report( $section_id, $date_from, $date_to, $order );
|
||
$rate_lookup = $this->build_consumption_rate_lookup( $rows );
|
||
$water_total = 0.0;
|
||
$power_total = 0.0;
|
||
$water_cost_total = 0.0;
|
||
$power_cost_total = 0.0;
|
||
$consumed_count = 0;
|
||
$section_totals = array();
|
||
|
||
foreach ( $rows as $row ) {
|
||
$row->unit_price = $this->get_consumption_unit_price( $row, $rate_lookup );
|
||
$row->calculated_cost = $this->calculate_consumption_cost( $row->consumption, $row->type, $row->unit_price );
|
||
|
||
if ( null === $row->consumption ) {
|
||
continue;
|
||
}
|
||
|
||
$consumed_count++;
|
||
|
||
if ( 'power' === $row->type ) {
|
||
$power_total += (float) $row->consumption;
|
||
if ( null !== $row->calculated_cost ) {
|
||
$power_cost_total += (float) $row->calculated_cost;
|
||
}
|
||
} else {
|
||
$water_total += (float) $row->consumption;
|
||
if ( null !== $row->calculated_cost ) {
|
||
$water_cost_total += (float) $row->calculated_cost;
|
||
}
|
||
}
|
||
|
||
$section_key = $row->section_id ? (int) $row->section_id : 0;
|
||
|
||
if ( ! isset( $section_totals[ $section_key ] ) ) {
|
||
$section_totals[ $section_key ] = array(
|
||
'name' => $row->section_name ? $row->section_name : __( 'Ohne Sparte', KGVVM_TEXT_DOMAIN ),
|
||
'water' => 0.0,
|
||
'power' => 0.0,
|
||
'water_cost' => 0.0,
|
||
'power_cost' => 0.0,
|
||
);
|
||
}
|
||
|
||
$section_totals[ $section_key ][ $row->type ] += (float) $row->consumption;
|
||
|
||
if ( null !== $row->calculated_cost ) {
|
||
$section_totals[ $section_key ][ 'power' === $row->type ? 'power_cost' : 'water_cost' ] += (float) $row->calculated_cost;
|
||
}
|
||
}
|
||
?>
|
||
<div class='wrap'>
|
||
<h1><?php echo esc_html__( 'Verbrauchsauswertung', KGVVM_TEXT_DOMAIN ); ?></h1>
|
||
<?php $this->render_notice(); ?>
|
||
|
||
<form method='get' class='kgvvm-toolbar'>
|
||
<input type='hidden' name='page' value='kgvvm-consumption' />
|
||
<div class='kgvvm-filters'>
|
||
<select name='section_id'>
|
||
<option value=''><?php echo esc_html__( 'Alle Sparten', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<?php foreach ( $sections as $section ) : ?>
|
||
<option value='<?php echo esc_attr( $section->id ); ?>' <?php selected( $section_id, $section->id ); ?>><?php echo esc_html( $section->name ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<label>
|
||
<?php echo esc_html__( 'Von', KGVVM_TEXT_DOMAIN ); ?>
|
||
<input type='date' name='date_from' value='<?php echo esc_attr( $date_from ); ?>' />
|
||
</label>
|
||
<label>
|
||
<?php echo esc_html__( 'Bis', KGVVM_TEXT_DOMAIN ); ?>
|
||
<input type='date' name='date_to' value='<?php echo esc_attr( $date_to ); ?>' />
|
||
</label>
|
||
<select name='order'>
|
||
<option value='DESC' <?php selected( $order, 'DESC' ); ?>><?php echo esc_html__( 'Neueste zuerst', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='ASC' <?php selected( $order, 'ASC' ); ?>><?php echo esc_html__( 'Älteste zuerst', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
</select>
|
||
<button class='button button-primary'><?php echo esc_html__( 'Auswerten', KGVVM_TEXT_DOMAIN ); ?></button>
|
||
</div>
|
||
</form>
|
||
|
||
<div class='kgvvm-grid'>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $this->format_meter_value_with_unit( $water_total, 'water' ) ); ?></h2>
|
||
<p><?php echo esc_html__( 'Wasserverbrauch im Zeitraum', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $this->format_meter_value_with_unit( $power_total, 'power' ) ); ?></h2>
|
||
<p><?php echo esc_html__( 'Stromverbrauch im Zeitraum', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $this->format_currency( $water_cost_total ) ); ?></h2>
|
||
<p><?php echo esc_html__( 'Wasserkosten im Zeitraum', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $this->format_currency( $power_cost_total ) ); ?></h2>
|
||
<p><?php echo esc_html__( 'Stromkosten im Zeitraum', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( number_format_i18n( $consumed_count, 0 ) ); ?></h2>
|
||
<p><?php echo esc_html__( 'Ausgewertete Ablesungen', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html__( 'Gesamtwerte je Sparte', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<?php if ( empty( $section_totals ) ) : ?>
|
||
<p><?php echo esc_html__( 'Für den gewählten Zeitraum liegen noch keine auswertbaren Verbrauchsdaten vor.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php else : ?>
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><?php echo esc_html__( 'Sparte', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Wasser', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Wasserkosten', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Strom', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Stromkosten', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ( $section_totals as $section_total ) : ?>
|
||
<tr>
|
||
<td><strong><?php echo esc_html( $section_total['name'] ); ?></strong></td>
|
||
<td><?php echo esc_html( $this->format_meter_value_with_unit( $section_total['water'], 'water' ) ); ?></td>
|
||
<td><?php echo esc_html( $section_total['water_cost'] > 0 ? $this->format_currency( $section_total['water_cost'] ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( $this->format_meter_value_with_unit( $section_total['power'], 'power' ) ); ?></td>
|
||
<td><?php echo esc_html( $section_total['power_cost'] > 0 ? $this->format_currency( $section_total['power_cost'] ) : '—' ); ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><?php echo esc_html__( 'Datum', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Sparte', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Parzelle', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Zähler', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Vorheriger Stand', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Aktueller Stand', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Verbrauch seit letzter Ablesung', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Preis je Einheit', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Kosten', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php if ( empty( $rows ) ) : ?>
|
||
<tr><td colspan='9'><?php echo esc_html__( 'Für die aktuelle Auswahl wurden keine Ablesungen gefunden.', KGVVM_TEXT_DOMAIN ); ?></td></tr>
|
||
<?php else : ?>
|
||
<?php foreach ( $rows as $row ) : ?>
|
||
<tr>
|
||
<td><?php echo esc_html( wp_date( 'd.m.Y', strtotime( $row->reading_date ) ) ); ?></td>
|
||
<td><?php echo esc_html( $row->section_name ? $row->section_name : '—' ); ?></td>
|
||
<td><?php echo esc_html( $row->parcel_label ? $row->parcel_label : '—' ); ?></td>
|
||
<td><?php echo esc_html( $this->meter_type_label( $row->type ) . ' – ' . $row->meter_number ); ?></td>
|
||
<td><?php echo esc_html( null !== $row->previous_value ? $this->format_meter_value_with_unit( $row->previous_value, $row->type ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( $this->format_meter_value_with_unit( $row->reading_value, $row->type ) ); ?></td>
|
||
<td><?php echo esc_html( null !== $row->consumption ? $this->format_meter_value_with_unit( $row->consumption, $row->type ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( null !== $row->unit_price ? $this->format_price_per_unit( $row->unit_price, 'power' === $row->type ? 'kWh' : 'm³' ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( null !== $row->calculated_cost ? $this->format_currency( $row->calculated_cost ) : '—' ); ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render annual cost management.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function render_costs_page() {
|
||
$this->require_cap( 'manage_kleingarten' );
|
||
|
||
$view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'list';
|
||
|
||
if ( 'statement' === $view ) {
|
||
$this->render_cost_statement_page();
|
||
return;
|
||
}
|
||
|
||
$selected_year = isset( $_GET['year'] ) ? absint( $_GET['year'] ) : (int) current_time( 'Y' );
|
||
$selected_year = $selected_year > 0 ? $selected_year : (int) current_time( 'Y' );
|
||
$search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
|
||
$edit_id = absint( isset( $_GET['id'] ) ? $_GET['id'] : 0 );
|
||
$edit_rate_section_id = absint( isset( $_GET['edit_rate_section_id'] ) ? $_GET['edit_rate_section_id'] : 0 );
|
||
$cost = $edit_id ? $this->costs->find( $edit_id ) : null;
|
||
|
||
if ( $cost && ! empty( $cost->entry_year ) ) {
|
||
$selected_year = (int) $cost->entry_year;
|
||
}
|
||
|
||
$years = $this->costs->get_years( $selected_year );
|
||
$sections = $this->sections->all_for_options( true );
|
||
$section_rates = $this->costs->get_section_prices( $selected_year );
|
||
$edit_rate = null;
|
||
|
||
foreach ( $section_rates as $section_rate ) {
|
||
if ( (int) $section_rate->section_id === $edit_rate_section_id ) {
|
||
$edit_rate = $section_rate;
|
||
break;
|
||
}
|
||
}
|
||
|
||
$rows = $this->costs->search(
|
||
array(
|
||
'year' => $selected_year,
|
||
's' => $search,
|
||
'orderby' => isset( $_GET['orderby'] ) ? sanitize_key( wp_unslash( $_GET['orderby'] ) ) : 'name',
|
||
'order' => isset( $_GET['order'] ) ? sanitize_key( wp_unslash( $_GET['order'] ) ) : 'ASC',
|
||
)
|
||
);
|
||
$entry_count = count( $rows );
|
||
$parcel_rows = array_values(
|
||
array_filter(
|
||
$this->parcels->search(),
|
||
static function( $parcel ) {
|
||
return isset( $parcel->status ) && 'inactive' !== $parcel->status;
|
||
}
|
||
)
|
||
);
|
||
$tenant_rows = $this->tenants->search( array( 'status' => 'active' ) );
|
||
$member_rows = $this->assignments->get_member_users();
|
||
$parcel_count = count( $parcel_rows );
|
||
$member_count = count( $member_rows );
|
||
$parcel_share = 0.0;
|
||
$member_share = 0.0;
|
||
$year_total = 0.0;
|
||
|
||
foreach ( $rows as $row ) {
|
||
$row->distribution_type = $this->get_cost_distribution_type( $row );
|
||
$row->unit_amount = $this->get_cost_unit_amount( $row, $parcel_count, $member_count );
|
||
$row->calculated_total_cost = $this->get_cost_total_amount( $row, $parcel_count, $member_count );
|
||
$year_total += (float) $row->calculated_total_cost;
|
||
|
||
if ( 'member' === $row->distribution_type ) {
|
||
$member_share += (float) $row->unit_amount;
|
||
} else {
|
||
$parcel_share += (float) $row->unit_amount;
|
||
}
|
||
}
|
||
|
||
if ( $cost ) {
|
||
$cost->distribution_type = $this->get_cost_distribution_type( $cost );
|
||
$cost->unit_amount = $this->get_cost_unit_amount( $cost, $parcel_count, $member_count );
|
||
$cost->calculated_total_cost = $this->get_cost_total_amount( $cost, $parcel_count, $member_count );
|
||
}
|
||
?>
|
||
<div class='wrap'>
|
||
<h1 class='wp-heading-inline'><?php echo esc_html__( 'Kosten', KGVVM_TEXT_DOMAIN ); ?></h1>
|
||
<?php if ( $cost ) : ?>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-costs', array( 'year' => $selected_year ) ) ); ?>' class='page-title-action'><?php echo esc_html__( 'Neuer Eintrag', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
<?php endif; ?>
|
||
<?php $this->render_notice(); ?>
|
||
|
||
<form method='get' class='kgvvm-toolbar' id='kgvvm-cost-filter-form'>
|
||
<input type='hidden' name='page' value='kgvvm-costs' />
|
||
<div class='kgvvm-filters'>
|
||
<label for='kgvvm-cost-year-filter'><?php echo esc_html__( 'Jahr', KGVVM_TEXT_DOMAIN ); ?></label>
|
||
<select name='year' id='kgvvm-cost-year-filter'>
|
||
<?php foreach ( $years as $year ) : ?>
|
||
<option value='<?php echo esc_attr( $year ); ?>' <?php selected( $selected_year, $year ); ?>><?php echo esc_html( $year ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<input type='search' name='s' value='<?php echo esc_attr( $search ); ?>' placeholder='<?php echo esc_attr__( 'Kostenposten suchen …', KGVVM_TEXT_DOMAIN ); ?>' />
|
||
<button class='button'><?php echo esc_html__( 'Filtern', KGVVM_TEXT_DOMAIN ); ?></button>
|
||
</div>
|
||
</form>
|
||
|
||
<div class='kgvvm-grid'>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $this->format_currency( $year_total ) ); ?></h2>
|
||
<p><?php echo esc_html( sprintf( __( 'Gesamtkosten im Jahr %s', KGVVM_TEXT_DOMAIN ), $selected_year ) ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( number_format_i18n( $entry_count, 0 ) ); ?></h2>
|
||
<p><?php echo esc_html( sprintf( __( 'Erfasste Einträge in %s', KGVVM_TEXT_DOMAIN ), $selected_year ) ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $parcel_count > 0 ? $this->format_currency( $parcel_share ) : '—' ); ?></h2>
|
||
<p><?php echo esc_html__( 'Summe je Parzelle', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $member_count > 0 ? $this->format_currency( $member_share ) : '—' ); ?></h2>
|
||
<p><?php echo esc_html__( 'Summe je Mitglied', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class='kgvvm-grid'>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $edit_rate ? __( 'Spartenpreise bearbeiten', KGVVM_TEXT_DOMAIN ) : __( 'Preise je Sparte', KGVVM_TEXT_DOMAIN ) ); ?></h2>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Strom- und Wasserpreise können je Jahr und je Sparte unterschiedlich hinterlegt und später wieder bearbeitet werden.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<form method='post'>
|
||
<?php wp_nonce_field( 'kgvvm_save_cost_prices' ); ?>
|
||
<input type='hidden' name='kgvvm_action' value='save_cost_prices' />
|
||
<table class='form-table kgvvm-form-table'>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-price-entry-year'><?php echo esc_html__( 'Jahr', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<select name='entry_year' id='kgvvm-price-entry-year'>
|
||
<?php foreach ( $years as $year ) : ?>
|
||
<option value='<?php echo esc_attr( $year ); ?>' <?php selected( $selected_year, $year ); ?>><?php echo esc_html( $year ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-cost-section-id'><?php echo esc_html__( 'Sparte', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<select name='section_id' id='kgvvm-cost-section-id' required>
|
||
<option value=''><?php echo esc_html__( 'Bitte wählen', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<?php foreach ( $sections as $section ) : ?>
|
||
<option value='<?php echo esc_attr( $section->id ); ?>' <?php selected( $edit_rate ? (int) $edit_rate->section_id : 0, $section->id ); ?>><?php echo esc_html( $section->name ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-power-price'><?php echo esc_html__( 'Preis pro kWh', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input type='number' min='0' step='0.0001' name='power_price_per_kwh' id='kgvvm-power-price' value='<?php echo esc_attr( $edit_rate && null !== $edit_rate->power_price_per_kwh ? (float) $edit_rate->power_price_per_kwh : '' ); ?>' /> € / kWh</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-water-price'><?php echo esc_html__( 'Preis pro m³', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input type='number' min='0' step='0.0001' name='water_price_per_m3' id='kgvvm-water-price' value='<?php echo esc_attr( $edit_rate && null !== $edit_rate->water_price_per_m3 ? (float) $edit_rate->water_price_per_m3 : '' ); ?>' /> € / m³</td>
|
||
</tr>
|
||
</table>
|
||
<?php submit_button( $edit_rate ? __( 'Spartenpreise aktualisieren', KGVVM_TEXT_DOMAIN ) : __( 'Spartenpreise speichern', KGVVM_TEXT_DOMAIN ), 'secondary' ); ?>
|
||
<?php if ( $edit_rate ) : ?>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-costs', array( 'year' => $selected_year ) ) ); ?>' class='button-secondary'><?php echo esc_html__( 'Abbrechen', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
<?php endif; ?>
|
||
</form>
|
||
</div>
|
||
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $cost ? __( 'Kostenposten bearbeiten', KGVVM_TEXT_DOMAIN ) : __( 'Kostenposten anlegen', KGVVM_TEXT_DOMAIN ) ); ?></h2>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Pro Jahr können beliebig viele Einträge mit einem Betrag pro Parzelle oder pro Mitglied hinterlegt werden.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<form method='post'>
|
||
<?php wp_nonce_field( 'kgvvm_save_cost' ); ?>
|
||
<input type='hidden' name='kgvvm_action' value='save_cost' />
|
||
<input type='hidden' name='id' value='<?php echo esc_attr( $cost ? $cost->id : 0 ); ?>' />
|
||
<table class='form-table kgvvm-form-table'>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-cost-entry-year'><?php echo esc_html__( 'Jahr', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<select name='entry_year' id='kgvvm-cost-entry-year'>
|
||
<?php foreach ( $years as $year ) : ?>
|
||
<option value='<?php echo esc_attr( $year ); ?>' <?php selected( $cost ? (int) $cost->entry_year : $selected_year, $year ); ?>><?php echo esc_html( $year ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-cost-name'><?php echo esc_html__( 'Name', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='name' id='kgvvm-cost-name' type='text' class='regular-text' required value='<?php echo esc_attr( $cost ? $cost->name : '' ); ?>' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-cost-distribution'><?php echo esc_html__( 'Gilt pro', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<select name='distribution_type' id='kgvvm-cost-distribution'>
|
||
<option value='parcel' <?php selected( $cost ? $cost->distribution_type : 'parcel', 'parcel' ); ?>><?php echo esc_html__( 'Parzelle', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='member' <?php selected( $cost ? $cost->distribution_type : 'parcel', 'member' ); ?>><?php echo esc_html__( 'Mitglied', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-cost-unit-amount'><?php echo esc_html__( 'Betrag', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<input name='unit_amount' id='kgvvm-cost-unit-amount' type='number' min='0' step='0.01' required value='<?php echo esc_attr( $cost ? (float) $cost->unit_amount : '' ); ?>' />
|
||
<span>€</span>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Betrag je ausgewählter Einheit, also pro Parzelle oder pro Mitglied.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-cost-note'><?php echo esc_html__( 'Bemerkung', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><textarea name='note' id='kgvvm-cost-note' rows='4' class='large-text'><?php echo esc_textarea( $cost ? $cost->note : '' ); ?></textarea></td>
|
||
</tr>
|
||
</table>
|
||
<?php submit_button( $cost ? __( 'Kostenposten aktualisieren', KGVVM_TEXT_DOMAIN ) : __( 'Kostenposten speichern', KGVVM_TEXT_DOMAIN ) ); ?>
|
||
</form>
|
||
</div>
|
||
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html__( 'Jahresabrechnung drucken', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Die Übersicht kann im Browser direkt gedruckt oder als PDF gespeichert werden.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
|
||
<form method='get' target='_blank' style='margin-bottom:12px;'>
|
||
<input type='hidden' name='page' value='kgvvm-costs' />
|
||
<input type='hidden' name='view' value='statement' />
|
||
<input type='hidden' name='statement_type' value='parcel' />
|
||
<input type='hidden' name='year' value='<?php echo esc_attr( $selected_year ); ?>' />
|
||
<p>
|
||
<label for='kgvvm-statement-parcel'><?php echo esc_html__( 'Parzelle', KGVVM_TEXT_DOMAIN ); ?></label><br />
|
||
<select name='subject_id' id='kgvvm-statement-parcel' required>
|
||
<option value=''><?php echo esc_html__( 'Parzelle wählen', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<?php foreach ( $parcel_rows as $parcel ) : ?>
|
||
<option value='<?php echo esc_attr( $parcel->id ); ?>'><?php echo esc_html( $parcel->label . ( $parcel->section_name ? ' – ' . $parcel->section_name : '' ) ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</p>
|
||
<?php submit_button( __( 'Parzellenabrechnung öffnen', KGVVM_TEXT_DOMAIN ), 'secondary', 'submit', false ); ?>
|
||
</form>
|
||
|
||
<form method='get' target='_blank'>
|
||
<input type='hidden' name='page' value='kgvvm-costs' />
|
||
<input type='hidden' name='view' value='statement' />
|
||
<input type='hidden' name='statement_type' value='tenant' />
|
||
<input type='hidden' name='year' value='<?php echo esc_attr( $selected_year ); ?>' />
|
||
<p>
|
||
<label for='kgvvm-statement-tenant'><?php echo esc_html__( 'Pächter', KGVVM_TEXT_DOMAIN ); ?></label><br />
|
||
<select name='subject_id' id='kgvvm-statement-tenant' required>
|
||
<option value=''><?php echo esc_html__( 'Pächter wählen', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<?php foreach ( $tenant_rows as $tenant ) : ?>
|
||
<option value='<?php echo esc_attr( $tenant->id ); ?>'><?php echo esc_html( $tenant->last_name . ', ' . $tenant->first_name ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</p>
|
||
<?php submit_button( __( 'Pächterabrechnung öffnen', KGVVM_TEXT_DOMAIN ), 'secondary', 'submit', false ); ?>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( sprintf( __( 'Preise je Sparte im Jahr %s', KGVVM_TEXT_DOMAIN ), $selected_year ) ); ?></h2>
|
||
<?php if ( empty( $section_rates ) ) : ?>
|
||
<p><?php echo esc_html__( 'Für dieses Jahr sind noch keine Spartenpreise hinterlegt.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php else : ?>
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><?php echo esc_html__( 'Sparte', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Preis pro kWh', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Preis pro m³', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Aktualisiert', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Aktionen', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ( $section_rates as $rate ) : ?>
|
||
<tr>
|
||
<td><strong><?php echo esc_html( $rate->section_name ? $rate->section_name : '—' ); ?></strong></td>
|
||
<td><?php echo esc_html( null !== $rate->power_price_per_kwh ? $this->format_price_per_unit( $rate->power_price_per_kwh, 'kWh' ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( null !== $rate->water_price_per_m3 ? $this->format_price_per_unit( $rate->water_price_per_m3, 'm³' ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( ! empty( $rate->updated_at ) ? wp_date( 'd.m.Y H:i', strtotime( $rate->updated_at ) ) : '—' ); ?></td>
|
||
<td><a href='<?php echo esc_url( $this->admin_url( 'kgvvm-costs', array( 'year' => $selected_year, 'edit_rate_section_id' => $rate->section_id ) ) ); ?>'><?php echo esc_html__( 'Bearbeiten', KGVVM_TEXT_DOMAIN ); ?></a></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-costs', 'name' ) ); ?>'><?php echo esc_html__( 'Name', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><?php echo esc_html__( 'Verteilung', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Betrag', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-costs', 'total_cost' ) ); ?>'><?php echo esc_html__( 'Gesamt im Jahr', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><?php echo esc_html__( 'Bemerkung', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-costs', 'updated_at' ) ); ?>'><?php echo esc_html__( 'Aktualisiert', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><?php echo esc_html__( 'Aktionen', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php if ( empty( $rows ) ) : ?>
|
||
<tr><td colspan='7'><?php echo esc_html__( 'Für das gewählte Jahr sind noch keine Kostenposten vorhanden.', KGVVM_TEXT_DOMAIN ); ?></td></tr>
|
||
<?php else : ?>
|
||
<?php foreach ( $rows as $row ) : ?>
|
||
<tr>
|
||
<td><strong><?php echo esc_html( $row->name ); ?></strong></td>
|
||
<td><?php echo esc_html( $this->get_cost_distribution_label( $row->distribution_type ) ); ?></td>
|
||
<td><?php echo esc_html( $this->format_currency( $row->unit_amount ) ); ?></td>
|
||
<td><?php echo esc_html( $this->format_currency( $row->calculated_total_cost ) ); ?></td>
|
||
<td><?php echo esc_html( $row->note ? $row->note : '—' ); ?></td>
|
||
<td><?php echo esc_html( ! empty( $row->updated_at ) ? wp_date( 'd.m.Y H:i', strtotime( $row->updated_at ) ) : '—' ); ?></td>
|
||
<td>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-costs', array( 'year' => $selected_year, 'id' => $row->id ) ) ); ?>'><?php echo esc_html__( 'Bearbeiten', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
|
|
||
<a href='<?php echo esc_url( wp_nonce_url( $this->admin_url( 'kgvvm-costs', array( 'kgvvm_action' => 'delete_cost', 'id' => $row->id, 'year' => $selected_year ) ), 'kgvvm_delete_cost_' . $row->id ) ); ?>' onclick='return confirm("<?php echo esc_js( __( 'Kostenposten wirklich löschen?', KGVVM_TEXT_DOMAIN ) ); ?>");'><?php echo esc_html__( 'Löschen', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
|
||
<div class='kgvvm-grid'>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html__( 'Verteilung pro Parzelle', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<?php if ( empty( $parcel_rows ) ) : ?>
|
||
<p><?php echo esc_html__( 'Aktuell sind keine aktiven Parzellen für die Verteilung vorhanden.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php else : ?>
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><?php echo esc_html__( 'Parzelle', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Sparte', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Status', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Anzahl Pächter', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Jahresanteil', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ( $parcel_rows as $parcel ) : ?>
|
||
<tr>
|
||
<td><strong><?php echo esc_html( $parcel->label ); ?></strong></td>
|
||
<td><?php echo esc_html( $parcel->section_name ? $parcel->section_name : '—' ); ?></td>
|
||
<td><?php echo esc_html( $this->parcel_status_label( $parcel->status ) ); ?></td>
|
||
<td><?php echo esc_html( ! empty( $parcel->tenant_count ) ? number_format_i18n( (int) $parcel->tenant_count, 0 ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( $this->format_currency( $parcel_share ) ); ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html__( 'Verteilung pro Mitglied', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<?php if ( empty( $member_rows ) ) : ?>
|
||
<p><?php echo esc_html__( 'Aktuell sind keine Mitglieder für die Verteilung vorhanden.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php else : ?>
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><?php echo esc_html__( 'Mitglied', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'E-Mail', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Jahresanteil', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ( $member_rows as $member ) : ?>
|
||
<tr>
|
||
<td><strong><?php echo esc_html( $member->display_name ); ?></strong></td>
|
||
<td><?php echo esc_html( $member->user_email ? $member->user_email : '—' ); ?></td>
|
||
<td><?php echo esc_html( $member_count > 0 ? $this->format_currency( $member_share ) : '—' ); ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
var filterForm = document.getElementById('kgvvm-cost-filter-form');
|
||
var fields = [
|
||
document.getElementById('kgvvm-cost-year-filter'),
|
||
document.getElementById('kgvvm-price-entry-year'),
|
||
document.getElementById('kgvvm-cost-entry-year')
|
||
].filter(Boolean);
|
||
|
||
if (!fields.length) {
|
||
return;
|
||
}
|
||
|
||
fields.forEach(function (field) {
|
||
field.addEventListener('change', function () {
|
||
fields.forEach(function (otherField) {
|
||
otherField.value = field.value;
|
||
});
|
||
|
||
if (field.id === 'kgvvm-cost-year-filter' && filterForm) {
|
||
filterForm.submit();
|
||
}
|
||
});
|
||
});
|
||
});
|
||
</script>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render one printable annual statement for a parcel or tenant.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function render_cost_statement_page() {
|
||
$settings = wp_parse_args( get_option( 'kgvvm_settings', array() ), $this->get_settings_defaults() );
|
||
$year = isset( $_GET['year'] ) ? absint( $_GET['year'] ) : (int) current_time( 'Y' );
|
||
$statement_type = isset( $_GET['statement_type'] ) ? sanitize_key( wp_unslash( $_GET['statement_type'] ) ) : 'parcel';
|
||
$output = isset( $_GET['output'] ) ? sanitize_key( wp_unslash( $_GET['output'] ) ) : 'html';
|
||
$subject_id = absint( isset( $_GET['subject_id'] ) ? $_GET['subject_id'] : 0 );
|
||
$date_from = sprintf( '%04d-01-01', $year );
|
||
$date_to = sprintf( '%04d-12-31', $year );
|
||
$all_rows = $this->readings->get_consumption_report( 0, $date_from, $date_to, 'ASC' );
|
||
$active_parcels = array_values(
|
||
array_filter(
|
||
$this->parcels->search(),
|
||
static function( $parcel ) {
|
||
return isset( $parcel->status ) && 'inactive' !== $parcel->status;
|
||
}
|
||
)
|
||
);
|
||
$active_members = $this->assignments->get_member_users();
|
||
$cost_entries = $this->costs->search( array( 'year' => $year, 'orderby' => 'name', 'order' => 'ASC' ) );
|
||
$subject_label = '';
|
||
$subject_meta = array();
|
||
$parcel_ids = array();
|
||
$subject_parcel_count = 0;
|
||
$subject_member_count = 0;
|
||
|
||
if ( 'tenant' === $statement_type ) {
|
||
$tenant = $this->tenants->find( $subject_id );
|
||
|
||
if ( ! $tenant ) {
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Der gewünschte Pächter wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
|
||
}
|
||
|
||
$tenant_parcels = $this->assignments->get_parcels_for_tenant( $subject_id );
|
||
$tenant_members = $this->assignments->get_members_for_tenant( $subject_id );
|
||
$tenant_member_names = array_values( array_unique( array_filter( wp_list_pluck( $tenant_members, 'display_name' ) ) ) );
|
||
$parcel_ids = wp_list_pluck( $tenant_parcels, 'id' );
|
||
$subject_parcel_count = count( $tenant_parcels );
|
||
$subject_member_count = count( array_unique( array_filter( array_map( 'intval', wp_list_pluck( $tenant_members, 'ID' ) ) ) ) );
|
||
$subject_label = sprintf( __( 'Jahresabrechnung Pächter %1$s – %2$s', KGVVM_TEXT_DOMAIN ), $tenant->last_name . ', ' . $tenant->first_name, $year );
|
||
$subject_meta[] = array(
|
||
'label' => __( 'Mitglied/Pächter', KGVVM_TEXT_DOMAIN ),
|
||
'value' => empty( $tenant_member_names ) ? '—' : implode( ', ', $tenant_member_names ),
|
||
);
|
||
$subject_meta[] = array(
|
||
'label' => __( 'Pächter', KGVVM_TEXT_DOMAIN ),
|
||
'value' => $tenant->last_name . ', ' . $tenant->first_name,
|
||
);
|
||
$subject_meta[] = array(
|
||
'label' => __( 'Parzellen', KGVVM_TEXT_DOMAIN ),
|
||
'value' => empty( $tenant_parcels ) ? '—' : implode( ', ', wp_list_pluck( $tenant_parcels, 'label' ) ),
|
||
);
|
||
} else {
|
||
$parcel = $this->parcels->find( $subject_id );
|
||
|
||
if ( ! $parcel ) {
|
||
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Die gewünschte Parzelle wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
|
||
}
|
||
|
||
$section = $parcel->section_id ? $this->sections->find( $parcel->section_id ) : null;
|
||
$parcel_tenants = $this->assignments->get_tenants_for_parcel( $subject_id );
|
||
$parcel_members = $this->assignments->get_members_for_parcel( $subject_id );
|
||
$parcel_member_names = array_values( array_unique( array_filter( wp_list_pluck( $parcel_members, 'display_name' ) ) ) );
|
||
$parcel_ids = array( $subject_id );
|
||
$subject_parcel_count = 1;
|
||
$subject_member_count = count( $parcel_members );
|
||
$subject_label = sprintf( __( 'Jahresabrechnung Parzelle %1$s – %2$s', KGVVM_TEXT_DOMAIN ), $parcel->label, $year );
|
||
$subject_meta[] = array(
|
||
'label' => __( 'Parzelle', KGVVM_TEXT_DOMAIN ),
|
||
'value' => $parcel->label,
|
||
);
|
||
$subject_meta[] = array(
|
||
'label' => __( 'Sparte', KGVVM_TEXT_DOMAIN ),
|
||
'value' => $section ? $section->name : '—',
|
||
);
|
||
$subject_meta[] = array(
|
||
'label' => __( 'Mitglied/Pächter', KGVVM_TEXT_DOMAIN ),
|
||
'value' => empty( $parcel_member_names ) ? '—' : implode( ', ', $parcel_member_names ),
|
||
);
|
||
$subject_meta[] = array(
|
||
'label' => __( 'Pächter', KGVVM_TEXT_DOMAIN ),
|
||
'value' => empty( $parcel_tenants ) ? '—' : implode( ', ', array_map( array( $this, 'format_tenant_full_name' ), $parcel_tenants ) ),
|
||
);
|
||
}
|
||
|
||
$statement_rows = array();
|
||
|
||
foreach ( $all_rows as $row ) {
|
||
if ( in_array( (int) $row->parcel_id, array_map( 'intval', $parcel_ids ), true ) ) {
|
||
$statement_rows[] = $row;
|
||
}
|
||
}
|
||
|
||
$rate_lookup = $this->build_consumption_rate_lookup( $statement_rows );
|
||
$water_total = 0.0;
|
||
$power_total = 0.0;
|
||
$water_cost_total = 0.0;
|
||
$power_cost_total = 0.0;
|
||
$utility_rows = array();
|
||
|
||
foreach ( $statement_rows as $row ) {
|
||
$row->unit_price = $this->get_consumption_unit_price( $row, $rate_lookup );
|
||
$row->calculated_cost = $this->calculate_consumption_cost( $row->consumption, $row->type, $row->unit_price );
|
||
|
||
if ( null === $row->consumption ) {
|
||
continue;
|
||
}
|
||
|
||
$utility_rows[] = $row;
|
||
|
||
if ( 'power' === $row->type ) {
|
||
$power_total += (float) $row->consumption;
|
||
if ( null !== $row->calculated_cost ) {
|
||
$power_cost_total += (float) $row->calculated_cost;
|
||
}
|
||
} else {
|
||
$water_total += (float) $row->consumption;
|
||
if ( null !== $row->calculated_cost ) {
|
||
$water_cost_total += (float) $row->calculated_cost;
|
||
}
|
||
}
|
||
}
|
||
|
||
$fixed_items = array();
|
||
$fixed_total = 0.0;
|
||
|
||
foreach ( $cost_entries as $entry ) {
|
||
$distribution_type = $this->get_cost_distribution_type( $entry );
|
||
$unit_amount = $this->get_cost_unit_amount( $entry, count( $active_parcels ), count( $active_members ) );
|
||
$subject_units = 'member' === $distribution_type ? $subject_member_count : $subject_parcel_count;
|
||
$share = $unit_amount * max( 0, $subject_units );
|
||
$total_amount = $this->get_cost_total_amount( $entry, count( $active_parcels ), count( $active_members ) );
|
||
$fixed_total += $share;
|
||
$fixed_items[] = array(
|
||
'name' => $entry->name,
|
||
'distribution_label' => $this->get_cost_distribution_label( $distribution_type ),
|
||
'unit_amount' => $unit_amount,
|
||
'units' => $subject_units,
|
||
'total' => $total_amount,
|
||
'share' => $share,
|
||
'note' => $entry->note,
|
||
);
|
||
}
|
||
|
||
$utility_total = $water_cost_total + $power_cost_total;
|
||
$grand_total = $fixed_total + $utility_total;
|
||
|
||
if ( 'pdf' === $output ) {
|
||
$this->render_cost_statement_pdf(
|
||
array(
|
||
'year' => $year,
|
||
'statement_type' => $statement_type,
|
||
'subject_id' => $subject_id,
|
||
'subject_label' => $subject_label,
|
||
'subject_meta' => $subject_meta,
|
||
'fixed_items' => $fixed_items,
|
||
'utility_rows' => $utility_rows,
|
||
'fixed_total' => $fixed_total,
|
||
'water_cost_total' => $water_cost_total,
|
||
'power_cost_total' => $power_cost_total,
|
||
'grand_total' => $grand_total,
|
||
'settings' => $settings,
|
||
)
|
||
);
|
||
return;
|
||
}
|
||
?>
|
||
<div class='wrap kgvvm-print-page'>
|
||
<h1><?php echo esc_html( $subject_label ); ?></h1>
|
||
<div class='kgvvm-print-actions'>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-costs', array( 'view' => 'statement', 'statement_type' => $statement_type, 'subject_id' => $subject_id, 'year' => $year, 'output' => 'pdf' ) ) ); ?>' class='button button-primary' target='_blank'><?php echo esc_html__( 'PDF erzeugen', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
<a href='#' class='button button-secondary' onclick='window.print(); return false;'><?php echo esc_html__( 'Drucken / Als PDF speichern', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-costs', array( 'year' => $year ) ) ); ?>' class='button-secondary'><?php echo esc_html__( 'Zurück', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
</div>
|
||
|
||
<?php if ( ! empty( $settings['pdf_club_name'] ) || ! empty( $settings['pdf_logo_url'] ) || ! empty( $settings['pdf_contact_block'] ) || ! empty( $settings['pdf_intro_text'] ) ) : ?>
|
||
<div class='kgvvm-card'>
|
||
<table style='width:100%; border-collapse:collapse;'>
|
||
<tr>
|
||
<?php if ( ! empty( $settings['pdf_logo_url'] ) ) : ?>
|
||
<td style='width:140px; vertical-align:top;'>
|
||
<img src='<?php echo esc_url( $settings['pdf_logo_url'] ); ?>' alt='' style='max-width:120px; height:auto;' />
|
||
</td>
|
||
<?php endif; ?>
|
||
<td style='vertical-align:top;'>
|
||
<?php if ( ! empty( $settings['pdf_club_name'] ) ) : ?>
|
||
<h2 style='margin-top:0;'><?php echo esc_html( $settings['pdf_club_name'] ); ?></h2>
|
||
<?php endif; ?>
|
||
<?php if ( ! empty( $settings['pdf_contact_block'] ) ) : ?>
|
||
<p><?php echo wp_kses_post( nl2br( esc_html( $settings['pdf_contact_block'] ) ) ); ?></p>
|
||
<?php endif; ?>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
<?php if ( ! empty( $settings['pdf_intro_text'] ) ) : ?>
|
||
<p><?php echo wp_kses_post( nl2br( esc_html( $settings['pdf_intro_text'] ) ) ); ?></p>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class='kgvvm-card'>
|
||
<ul>
|
||
<?php foreach ( $subject_meta as $meta ) : ?>
|
||
<li><strong><?php echo esc_html( $meta['label'] ); ?>:</strong> <?php echo esc_html( $meta['value'] ); ?></li>
|
||
<?php endforeach; ?>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class='kgvvm-grid'>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $this->format_currency( $fixed_total ) ); ?></h2>
|
||
<p><?php echo esc_html__( 'Anteilige Grundkosten', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $this->format_currency( $water_cost_total ) ); ?></h2>
|
||
<p><?php echo esc_html__( 'Wasserkosten', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $this->format_currency( $power_cost_total ) ); ?></h2>
|
||
<p><?php echo esc_html__( 'Stromkosten', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html( $this->format_currency( $grand_total ) ); ?></h2>
|
||
<p><?php echo esc_html__( 'Gesamtbetrag', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html__( 'Grundkosten anteilig', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<?php if ( empty( $fixed_items ) ) : ?>
|
||
<p><?php echo esc_html__( 'Für dieses Jahr sind keine allgemeinen Kostenposten hinterlegt.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php else : ?>
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><?php echo esc_html__( 'Kostenposten', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Gesamtkosten', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Anteil', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ( $fixed_items as $item ) : ?>
|
||
<tr>
|
||
<td>
|
||
<strong><?php echo esc_html( $item['name'] ); ?></strong>
|
||
<br /><span class="kgvvm-help"><?php echo esc_html( sprintf( __( '%1$s × %2$s zu je %3$s', KGVVM_TEXT_DOMAIN ), $item['distribution_label'], number_format_i18n( (int) $item['units'], 0 ), $this->format_currency( $item['unit_amount'] ) ) ); ?></span>
|
||
<?php echo $item['note'] ? '<br /><span class="kgvvm-help">' . esc_html( $item['note'] ) . '</span>' : ''; ?>
|
||
</td>
|
||
<td><?php echo esc_html( $this->format_currency( $item['total'] ) ); ?></td>
|
||
<td><?php echo esc_html( $this->format_currency( $item['share'] ) ); ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html__( 'Wasserzähler', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<?php echo $this->build_statement_meter_detail_table( $this->get_statement_rows_by_meter_type( $utility_rows, 'water' ), 'water' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||
</div>
|
||
|
||
<div class='kgvvm-card'>
|
||
<h2><?php echo esc_html__( 'Stromzähler', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<?php echo $this->build_statement_meter_detail_table( $this->get_statement_rows_by_meter_type( $utility_rows, 'power' ), 'power' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||
</div>
|
||
|
||
<?php if ( ! empty( $settings['pdf_footer_text'] ) ) : ?>
|
||
<div class='kgvvm-card'>
|
||
<p><?php echo wp_kses_post( nl2br( esc_html( $settings['pdf_footer_text'] ) ) ); ?></p>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Output one annual statement as a real PDF using TCPDF.
|
||
*
|
||
* @param array $statement Statement payload.
|
||
* @return void
|
||
*/
|
||
private function render_cost_statement_pdf( $statement ) {
|
||
$tcpdf_path = KGVVM_PLUGIN_DIR . 'lib/tcpdf/tcpdf.php';
|
||
$settings = isset( $statement['settings'] ) && is_array( $statement['settings'] ) ? wp_parse_args( $statement['settings'], $this->get_settings_defaults() ) : $this->get_settings_defaults();
|
||
$document_author = ! empty( $settings['pdf_club_name'] ) ? $settings['pdf_club_name'] : 'KGV Vereinsverwaltung';
|
||
|
||
if ( function_exists( 'nocache_headers' ) ) {
|
||
nocache_headers();
|
||
}
|
||
|
||
while ( ob_get_level() > 0 ) {
|
||
ob_end_clean();
|
||
}
|
||
|
||
if ( ! class_exists( '\\TCPDF' ) && file_exists( $tcpdf_path ) ) {
|
||
require_once $tcpdf_path;
|
||
}
|
||
|
||
if ( ! class_exists( '\\TCPDF' ) ) {
|
||
wp_die( esc_html__( 'TCPDF konnte nicht geladen werden. Bitte die Bibliothek prüfen.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
$pdf = new \TCPDF( PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false );
|
||
$pdf->SetCreator( $document_author );
|
||
$pdf->SetAuthor( $document_author );
|
||
$pdf->SetTitle( $statement['subject_label'] );
|
||
$pdf->SetMargins( 12, 12, 12 );
|
||
$pdf->SetAutoPageBreak( true, 12 );
|
||
$pdf->setPrintHeader( false );
|
||
$pdf->setPrintFooter( false );
|
||
$pdf->AddPage();
|
||
$pdf->SetFont( 'dejavusans', '', 10 );
|
||
$pdf->writeHTML( $this->build_cost_statement_pdf_html( $statement ), true, false, true, false, '' );
|
||
|
||
$filename = sanitize_file_name( sprintf( 'jahresabrechnung-%s-%s-%s.pdf', $statement['year'], $statement['statement_type'], $statement['subject_id'] ) );
|
||
$pdf->Output( $filename, 'I' );
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* Build the HTML markup for the PDF statement.
|
||
*
|
||
* @param array $statement Statement payload.
|
||
* @return string
|
||
*/
|
||
private function build_cost_statement_pdf_html( $statement ) {
|
||
$settings = isset( $statement['settings'] ) && is_array( $statement['settings'] ) ? wp_parse_args( $statement['settings'], $this->get_settings_defaults() ) : $this->get_settings_defaults();
|
||
ob_start();
|
||
?>
|
||
<?php if ( ! empty( $settings['pdf_club_name'] ) || ! empty( $settings['pdf_logo_url'] ) || ! empty( $settings['pdf_contact_block'] ) || ! empty( $settings['pdf_intro_text'] ) ) : ?>
|
||
<table cellpadding="4" cellspacing="0" border="0">
|
||
<tr>
|
||
<?php if ( ! empty( $settings['pdf_logo_url'] ) ) : ?>
|
||
<td width="120">
|
||
<img src="<?php echo esc_url( $settings['pdf_logo_url'] ); ?>" style="max-width:100px; height:auto;" alt="" />
|
||
</td>
|
||
<?php endif; ?>
|
||
<td>
|
||
<?php if ( ! empty( $settings['pdf_club_name'] ) ) : ?>
|
||
<h2><?php echo esc_html( $settings['pdf_club_name'] ); ?></h2>
|
||
<?php endif; ?>
|
||
<?php if ( ! empty( $settings['pdf_contact_block'] ) ) : ?>
|
||
<p><?php echo nl2br( esc_html( $settings['pdf_contact_block'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p>
|
||
<?php endif; ?>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
<?php if ( ! empty( $settings['pdf_intro_text'] ) ) : ?>
|
||
<p><?php echo nl2br( esc_html( $settings['pdf_intro_text'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p>
|
||
<?php endif; ?>
|
||
<?php endif; ?>
|
||
|
||
<h1><?php echo esc_html( $statement['subject_label'] ); ?></h1>
|
||
<table cellpadding="4" cellspacing="0" border="0">
|
||
<?php foreach ( $statement['subject_meta'] as $meta ) : ?>
|
||
<tr>
|
||
<td><strong><?php echo esc_html( $meta['label'] ); ?>:</strong></td>
|
||
<td><?php echo esc_html( $meta['value'] ); ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</table>
|
||
|
||
<h2><?php echo esc_html__( 'Zusammenfassung', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<table cellpadding="5" cellspacing="0" border="1">
|
||
<tr>
|
||
<th><strong><?php echo esc_html__( 'Position', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
||
<th><strong><?php echo esc_html__( 'Betrag', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
||
</tr>
|
||
<tr>
|
||
<td><?php echo esc_html__( 'Anteilige Grundkosten', KGVVM_TEXT_DOMAIN ); ?></td>
|
||
<td><?php echo esc_html( $this->format_currency( $statement['fixed_total'] ) ); ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td><?php echo esc_html__( 'Wasserkosten', KGVVM_TEXT_DOMAIN ); ?></td>
|
||
<td><?php echo esc_html( $this->format_currency( $statement['water_cost_total'] ) ); ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td><?php echo esc_html__( 'Stromkosten', KGVVM_TEXT_DOMAIN ); ?></td>
|
||
<td><?php echo esc_html( $this->format_currency( $statement['power_cost_total'] ) ); ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong><?php echo esc_html__( 'Gesamtbetrag', KGVVM_TEXT_DOMAIN ); ?></strong></td>
|
||
<td><strong><?php echo esc_html( $this->format_currency( $statement['grand_total'] ) ); ?></strong></td>
|
||
</tr>
|
||
</table>
|
||
|
||
<h2><?php echo esc_html__( 'Grundkosten anteilig', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<?php if ( empty( $statement['fixed_items'] ) ) : ?>
|
||
<p><?php echo esc_html__( 'Keine allgemeinen Kostenposten vorhanden.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php else : ?>
|
||
<table cellpadding="5" cellspacing="0" border="1">
|
||
<tr>
|
||
<th><strong><?php echo esc_html__( 'Kostenposten', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
||
<th><strong><?php echo esc_html__( 'Gesamtkosten', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
||
<th><strong><?php echo esc_html__( 'Anteil', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
||
</tr>
|
||
<?php foreach ( $statement['fixed_items'] as $item ) : ?>
|
||
<tr>
|
||
<td><?php echo esc_html( $item['name'] . ' – ' . sprintf( __( '%1$s × %2$s zu je %3$s', KGVVM_TEXT_DOMAIN ), $item['distribution_label'], number_format_i18n( (int) $item['units'], 0 ), $this->format_currency( $item['unit_amount'] ) ) ); ?></td>
|
||
<td><?php echo esc_html( $this->format_currency( $item['total'] ) ); ?></td>
|
||
<td><?php echo esc_html( $this->format_currency( $item['share'] ) ); ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</table>
|
||
<?php endif; ?>
|
||
|
||
<h2><?php echo esc_html__( 'Wasserzähler', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<?php echo $this->build_statement_meter_detail_table( $this->get_statement_rows_by_meter_type( $statement['utility_rows'], 'water' ), 'water', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||
|
||
<h2><?php echo esc_html__( 'Stromzähler', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<?php echo $this->build_statement_meter_detail_table( $this->get_statement_rows_by_meter_type( $statement['utility_rows'], 'power' ), 'power', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||
|
||
<?php if ( ! empty( $settings['pdf_footer_text'] ) ) : ?>
|
||
<p style="margin-top:16px;"><?php echo nl2br( esc_html( $settings['pdf_footer_text'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p>
|
||
<?php endif; ?>
|
||
<?php
|
||
return (string) ob_get_clean();
|
||
}
|
||
|
||
/**
|
||
* Render section admin page.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function render_sections_page() {
|
||
$this->require_cap( 'edit_sparten' );
|
||
$view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'list';
|
||
|
||
if ( 'form' === $view ) {
|
||
$this->render_section_form();
|
||
return;
|
||
}
|
||
|
||
$rows = $this->sections->search( $_GET );
|
||
?>
|
||
<div class='wrap'>
|
||
<h1 class='wp-heading-inline'><?php echo esc_html__( 'Sparten', KGVVM_TEXT_DOMAIN ); ?></h1>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-sparten', array( 'view' => 'form' ) ) ); ?>' class='page-title-action'><?php echo esc_html__( 'Neue Sparte', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
<?php $this->render_notice(); ?>
|
||
|
||
<form method='get' class='kgvvm-toolbar'>
|
||
<input type='hidden' name='page' value='kgvvm-sparten' />
|
||
<div class='kgvvm-filters'>
|
||
<input type='search' name='s' value='<?php echo esc_attr( isset( $_GET['s'] ) ? wp_unslash( $_GET['s'] ) : '' ); ?>' placeholder='<?php echo esc_attr__( 'Sparte suchen …', KGVVM_TEXT_DOMAIN ); ?>' />
|
||
<select name='status'>
|
||
<option value=''><?php echo esc_html__( 'Alle Status', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='active' <?php selected( isset( $_GET['status'] ) ? wp_unslash( $_GET['status'] ) : '', 'active' ); ?>><?php echo esc_html__( 'Aktiv', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='inactive' <?php selected( isset( $_GET['status'] ) ? wp_unslash( $_GET['status'] ) : '', 'inactive' ); ?>><?php echo esc_html__( 'Inaktiv', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
</select>
|
||
<button class='button'><?php echo esc_html__( 'Filtern', KGVVM_TEXT_DOMAIN ); ?></button>
|
||
</div>
|
||
</form>
|
||
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-sparten', 'name' ) ); ?>'><?php echo esc_html__( 'Name', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><?php echo esc_html__( 'Beschreibung', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Hauptzähler', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-sparten', 'status' ) ); ?>'><?php echo esc_html__( 'Status', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><?php echo esc_html__( 'Aktionen', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php if ( empty( $rows ) ) : ?>
|
||
<tr><td colspan='5'><?php echo esc_html__( 'Noch keine Sparten vorhanden.', KGVVM_TEXT_DOMAIN ); ?></td></tr>
|
||
<?php else : ?>
|
||
<?php foreach ( $rows as $row ) : ?>
|
||
<?php $main_meters = $this->meters->get_main_for_section( $row->id ); ?>
|
||
<tr>
|
||
<td><strong><?php echo esc_html( $row->name ); ?></strong></td>
|
||
<td><?php echo esc_html( $row->description ? $row->description : '—' ); ?></td>
|
||
<td>
|
||
<?php if ( empty( $main_meters ) ) : ?>
|
||
—
|
||
<?php else : ?>
|
||
<?php echo esc_html( implode( ', ', array_map( array( $this, 'format_meter_short_label' ), $main_meters ) ) ); ?>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td><?php echo $this->status_badge( 'active' === $row->status ? __( 'Aktiv', KGVVM_TEXT_DOMAIN ) : __( 'Inaktiv', KGVVM_TEXT_DOMAIN ), 'active' === $row->status ? 'green' : 'gray' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></td>
|
||
<td>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-sparten', array( 'view' => 'form', 'id' => $row->id ) ) ); ?>'><?php echo esc_html__( 'Bearbeiten', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
|
|
||
<a href='<?php echo esc_url( wp_nonce_url( $this->admin_url( 'kgvvm-sparten', array( 'kgvvm_action' => 'delete_section', 'id' => $row->id ) ), 'kgvvm_delete_section_' . $row->id ) ); ?>' onclick='return confirm("<?php echo esc_js( __( 'Sparte wirklich löschen?', KGVVM_TEXT_DOMAIN ) ); ?>");'><?php echo esc_html__( 'Löschen', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render section form.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function render_section_form() {
|
||
$id = absint( isset( $_GET['id'] ) ? $_GET['id'] : 0 );
|
||
$section = $id ? $this->sections->find( $id ) : null;
|
||
$selected_main_meters = $id ? $this->meters->get_main_for_section( $id ) : array();
|
||
$main_meter_options = $id ? $this->meters->get_available_main_for_section( $id ) : array();
|
||
$selected_main_meter_ids = $id ? wp_list_pluck( $selected_main_meters, 'id' ) : array();
|
||
$today = current_time( 'Y-m-d' );
|
||
?>
|
||
<div class='wrap'>
|
||
<h1><?php echo esc_html( $id ? __( 'Sparte bearbeiten', KGVVM_TEXT_DOMAIN ) : __( 'Neue Sparte', KGVVM_TEXT_DOMAIN ) ); ?></h1>
|
||
<?php $this->render_notice(); ?>
|
||
<form method='post'>
|
||
<?php wp_nonce_field( 'kgvvm_save_section' ); ?>
|
||
<input type='hidden' name='kgvvm_action' value='save_section' />
|
||
<input type='hidden' name='id' value='<?php echo esc_attr( $id ); ?>' />
|
||
<table class='form-table kgvvm-form-table'>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-section-name'><?php echo esc_html__( 'Name', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='name' id='kgvvm-section-name' type='text' class='regular-text' required value='<?php echo esc_attr( $section ? $section->name : '' ); ?>' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-section-description'><?php echo esc_html__( 'Beschreibung', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><textarea name='description' id='kgvvm-section-description' rows='5' class='large-text'><?php echo esc_textarea( $section ? $section->description : '' ); ?></textarea></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-section-main-meters'><?php echo esc_html__( 'Hauptzähler', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<?php if ( ! $id ) : ?>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Bitte die Sparte zuerst speichern. Danach können mehrere Hauptzähler zugeordnet werden.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php elseif ( empty( $main_meter_options ) ) : ?>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Für diese Sparte sind aktuell keine freien Zähler verfügbar. Bitte zuerst unter „Zähler“ passende Zähler anlegen.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php else : ?>
|
||
<select name='main_meter_ids[]' id='kgvvm-section-main-meters' multiple class='kgvvm-multiselect'>
|
||
<?php foreach ( $main_meter_options as $meter ) : ?>
|
||
<option value='<?php echo esc_attr( $meter->id ); ?>' <?php selected( in_array( (int) $meter->id, $selected_main_meter_ids, true ), true ); ?>><?php echo esc_html( $this->format_meter_short_label( $meter ) ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Hier können mehrere freie Zähler dieser Sparte als Hauptzähler markiert werden.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php endif; ?>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-section-status'><?php echo esc_html__( 'Status', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<select name='status' id='kgvvm-section-status'>
|
||
<option value='active' <?php selected( $section ? $section->status : 'active', 'active' ); ?>><?php echo esc_html__( 'Aktiv', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='inactive' <?php selected( $section ? $section->status : 'active', 'inactive' ); ?>><?php echo esc_html__( 'Inaktiv', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
<?php submit_button( $id ? __( 'Sparte aktualisieren', KGVVM_TEXT_DOMAIN ) : __( 'Sparte anlegen', KGVVM_TEXT_DOMAIN ) ); ?>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-sparten' ) ); ?>' class='button-secondary'><?php echo esc_html__( 'Zurück', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
</form>
|
||
|
||
<?php if ( $id && ! empty( $selected_main_meters ) ) : ?>
|
||
<div class='kgvvm-card' style='margin-top:24px;'>
|
||
<h2><?php echo esc_html__( 'Ablesungen der Hauptzähler', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Hier können für die Hauptzähler dieser Sparte direkt neue Ablesungen erfasst werden.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<div class='kgvvm-grid'>
|
||
<?php foreach ( $selected_main_meters as $main_meter ) : ?>
|
||
<?php
|
||
$latest_main = $this->readings->get_latest_for_meter( $main_meter->id );
|
||
$recent_main = $this->readings->get_all_for_meter( $main_meter->id, 5 );
|
||
$monthly_summary = $this->readings->get_monthly_summary_for_meter( $main_meter->id );
|
||
$step = 'water' === $main_meter->type ? '0.01' : '1';
|
||
?>
|
||
<div class='kgvvm-card'>
|
||
<h3><?php echo esc_html( $this->format_meter_short_label( $main_meter ) ); ?></h3>
|
||
<p>
|
||
<strong><?php echo esc_html__( 'Letzte Ablesung:', KGVVM_TEXT_DOMAIN ); ?></strong>
|
||
<?php if ( $latest_main ) : ?>
|
||
<?php echo esc_html( $this->format_meter_value_with_unit( $latest_main->reading_value, $main_meter->type ) . ' am ' . wp_date( 'd.m.Y', strtotime( $latest_main->reading_date ) ) ); ?>
|
||
<?php else : ?>
|
||
<?php echo esc_html__( 'Noch keine Ablesung vorhanden', KGVVM_TEXT_DOMAIN ); ?>
|
||
<?php endif; ?>
|
||
</p>
|
||
|
||
<form method='post'>
|
||
<?php wp_nonce_field( 'kgvvm_save_meter_reading' ); ?>
|
||
<input type='hidden' name='kgvvm_action' value='save_meter_reading' />
|
||
<input type='hidden' name='return_page' value='kgvvm-sparten' />
|
||
<input type='hidden' name='return_view' value='form' />
|
||
<input type='hidden' name='return_id' value='<?php echo esc_attr( $id ); ?>' />
|
||
<input type='hidden' name='parcel_id' value='0' />
|
||
<input type='hidden' name='meter_id' value='<?php echo esc_attr( $main_meter->id ); ?>' />
|
||
<p>
|
||
<label><?php echo esc_html__( 'Ablesedatum', KGVVM_TEXT_DOMAIN ); ?><br />
|
||
<input type='date' name='reading_date' required value='<?php echo esc_attr( $today ); ?>' />
|
||
</label>
|
||
</p>
|
||
<p>
|
||
<label><?php echo esc_html__( 'Zählerstand', KGVVM_TEXT_DOMAIN ); ?> (<?php echo esc_html( $this->meter_unit_label( $main_meter->type ) ); ?>)<br />
|
||
<input type='number' min='0' step='<?php echo esc_attr( $step ); ?>' name='reading_value' required />
|
||
</label>
|
||
</p>
|
||
<p>
|
||
<label><?php echo esc_html__( 'Notiz', KGVVM_TEXT_DOMAIN ); ?><br />
|
||
<textarea name='note' rows='3' class='large-text'></textarea>
|
||
</label>
|
||
</p>
|
||
<?php submit_button( __( 'Ablesung speichern', KGVVM_TEXT_DOMAIN ), 'secondary', 'submit', false ); ?>
|
||
</form>
|
||
|
||
<?php if ( ! empty( $recent_main ) ) : ?>
|
||
<table class='widefat striped' style='margin-top:12px;'>
|
||
<thead>
|
||
<tr>
|
||
<th><?php echo esc_html__( 'Datum', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Stand', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Flag', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ( $recent_main as $reading ) : ?>
|
||
<tr>
|
||
<td><?php echo esc_html( wp_date( 'd.m.Y', strtotime( $reading->reading_date ) ) ); ?></td>
|
||
<td><?php echo esc_html( $this->format_meter_value_with_unit( $reading->reading_value, $main_meter->type ) ); ?></td>
|
||
<td><?php echo wp_kses_post( $this->reading_flag_badge( $reading ) ); ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
|
||
<?php if ( ! empty( $monthly_summary ) ) : ?>
|
||
<h4 style='margin-top:16px;'><?php echo esc_html__( 'Monatsübersicht', KGVVM_TEXT_DOMAIN ); ?></h4>
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><?php echo esc_html__( 'Monat', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Von', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Bis', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Verbrauch', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Ablesungen', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ( $monthly_summary as $month_row ) : ?>
|
||
<tr>
|
||
<td><?php echo esc_html( wp_date( 'm.Y', strtotime( $month_row['month'] . '-01' ) ) ); ?></td>
|
||
<td><?php echo esc_html( null !== $month_row['from_value'] ? $this->format_meter_value_with_unit( $month_row['from_value'], $main_meter->type ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( $this->format_meter_value_with_unit( $month_row['to_value'], $main_meter->type ) ); ?></td>
|
||
<td><?php echo esc_html( $this->format_meter_value_with_unit( $month_row['consumption'], $main_meter->type ) ); ?></td>
|
||
<td><?php echo esc_html( (string) $month_row['readings'] ); ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render parcel page.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function render_parcels_page() {
|
||
$this->require_cap( 'edit_parzellen' );
|
||
$view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'list';
|
||
|
||
if ( 'form' === $view ) {
|
||
$this->render_parcel_form();
|
||
return;
|
||
}
|
||
|
||
$paged = max( 1, absint( isset( $_GET['paged'] ) ? $_GET['paged'] : 1 ) );
|
||
$per_page = 50;
|
||
$total_items = $this->parcels->count_filtered( $_GET );
|
||
$total_pages = max( 1, (int) ceil( $total_items / $per_page ) );
|
||
$rows = $this->parcels->search( array_merge( $_GET, array( 'limit' => $per_page, 'paged' => $paged ) ) );
|
||
$sections = $this->sections->all_for_options();
|
||
?>
|
||
<div class='wrap'>
|
||
<h1 class='wp-heading-inline'><?php echo esc_html__( 'Parzellen', KGVVM_TEXT_DOMAIN ); ?></h1>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-parzellen', array( 'view' => 'form' ) ) ); ?>' class='page-title-action'><?php echo esc_html__( 'Neue Parzelle', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
<?php $this->render_notice(); ?>
|
||
|
||
<form method='get' class='kgvvm-toolbar'>
|
||
<input type='hidden' name='page' value='kgvvm-parzellen' />
|
||
<div class='kgvvm-filters'>
|
||
<input type='search' name='s' value='<?php echo esc_attr( isset( $_GET['s'] ) ? wp_unslash( $_GET['s'] ) : '' ); ?>' placeholder='<?php echo esc_attr__( 'Parzelle suchen …', KGVVM_TEXT_DOMAIN ); ?>' />
|
||
<select name='section_id'>
|
||
<option value=''><?php echo esc_html__( 'Alle Sparten', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<?php foreach ( $sections as $section ) : ?>
|
||
<option value='<?php echo esc_attr( $section->id ); ?>' <?php selected( isset( $_GET['section_id'] ) ? absint( $_GET['section_id'] ) : 0, $section->id ); ?>><?php echo esc_html( $section->name ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<select name='status'>
|
||
<option value=''><?php echo esc_html__( 'Alle Status', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='free' <?php selected( isset( $_GET['status'] ) ? wp_unslash( $_GET['status'] ) : '', 'free' ); ?>><?php echo esc_html__( 'Frei', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='assigned' <?php selected( isset( $_GET['status'] ) ? wp_unslash( $_GET['status'] ) : '', 'assigned' ); ?>><?php echo esc_html__( 'Vergeben', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='reserved' <?php selected( isset( $_GET['status'] ) ? wp_unslash( $_GET['status'] ) : '', 'reserved' ); ?>><?php echo esc_html__( 'Reserviert', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='inactive' <?php selected( isset( $_GET['status'] ) ? wp_unslash( $_GET['status'] ) : '', 'inactive' ); ?>><?php echo esc_html__( 'Inaktiv', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
</select>
|
||
<button class='button'><?php echo esc_html__( 'Filtern', KGVVM_TEXT_DOMAIN ); ?></button>
|
||
</div>
|
||
</form>
|
||
|
||
<div class='kgvvm-toolbar'>
|
||
<p class='kgvvm-help'>
|
||
<?php echo esc_html( sprintf( __( '%1$d Parzellen gesamt · Seite %2$d von %3$d', KGVVM_TEXT_DOMAIN ), (int) $total_items, (int) $paged, (int) $total_pages ) ); ?>
|
||
</p>
|
||
<?php if ( $total_items > $per_page ) : ?>
|
||
<div class='tablenav-pages'>
|
||
<?php
|
||
echo wp_kses_post(
|
||
paginate_links(
|
||
array(
|
||
'base' => add_query_arg( 'paged', '%#%' ),
|
||
'format' => '',
|
||
'current' => $paged,
|
||
'total' => $total_pages,
|
||
'prev_text' => '«',
|
||
'next_text' => '»',
|
||
)
|
||
)
|
||
);
|
||
?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-parzellen', 'label' ) ); ?>'><?php echo esc_html__( 'Parzelle', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><?php echo esc_html__( 'Sparte', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-parzellen', 'area' ) ); ?>'><?php echo esc_html__( 'Fläche', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-parzellen', 'annual_rent' ) ); ?>'><?php echo esc_html__( 'Pacht / Jahr', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><?php echo esc_html__( 'Wasserzähler', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Stromzähler', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Mitglieder', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Pächter', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-parzellen', 'status' ) ); ?>'><?php echo esc_html__( 'Status', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><?php echo esc_html__( 'Aktionen', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php if ( empty( $rows ) ) : ?>
|
||
<tr><td colspan='10'><?php echo esc_html__( 'Noch keine Parzellen vorhanden.', KGVVM_TEXT_DOMAIN ); ?></td></tr>
|
||
<?php else : ?>
|
||
<?php foreach ( $rows as $row ) : ?>
|
||
<tr>
|
||
<td><strong><?php echo esc_html( $row->label ); ?></strong></td>
|
||
<td><?php echo esc_html( $row->section_name ); ?></td>
|
||
<td><?php echo null !== $row->area ? esc_html( number_format_i18n( (float) $row->area, 2 ) . ' m²' ) : '—'; ?></td>
|
||
<td><?php echo isset( $row->annual_rent ) && null !== $row->annual_rent ? esc_html( $this->format_currency( $row->annual_rent ) ) : '—'; ?></td>
|
||
<td><?php echo esc_html( $row->water_meter_number ? $row->water_meter_number : '—' ); ?></td>
|
||
<td><?php echo esc_html( $row->power_meter_number ? $row->power_meter_number : '—' ); ?></td>
|
||
<td><?php echo esc_html( (string) $row->member_count ); ?></td>
|
||
<td><?php echo esc_html( (string) $row->tenant_count ); ?></td>
|
||
<td><?php echo $this->status_badge( $this->parcel_status_label( $row->status ), 'inactive' === $row->status ? 'gray' : ( 'reserved' === $row->status ? 'orange' : 'green' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></td>
|
||
<td>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-parzellen', array( 'view' => 'form', 'id' => $row->id ) ) ); ?>'><?php echo esc_html__( 'Bearbeiten', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
|
|
||
<a href='<?php echo esc_url( wp_nonce_url( $this->admin_url( 'kgvvm-parzellen', array( 'kgvvm_action' => 'delete_parcel', 'id' => $row->id ) ), 'kgvvm_delete_parcel_' . $row->id ) ); ?>' onclick='return confirm("<?php echo esc_js( __( 'Parzelle wirklich löschen?', KGVVM_TEXT_DOMAIN ) ); ?>");'><?php echo esc_html__( 'Löschen', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
|
||
<?php if ( $total_items > $per_page ) : ?>
|
||
<div class='kgvvm-toolbar'>
|
||
<div class='tablenav-pages'>
|
||
<?php
|
||
echo wp_kses_post(
|
||
paginate_links(
|
||
array(
|
||
'base' => add_query_arg( 'paged', '%#%' ),
|
||
'format' => '',
|
||
'current' => $paged,
|
||
'total' => $total_pages,
|
||
'prev_text' => '«',
|
||
'next_text' => '»',
|
||
)
|
||
)
|
||
);
|
||
?>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render parcel form.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function render_parcel_form() {
|
||
$id = absint( isset( $_GET['id'] ) ? $_GET['id'] : 0 );
|
||
$parcel = $id ? $this->parcels->find( $id ) : null;
|
||
$sections = $this->sections->all_for_options( true );
|
||
$selected_section = $parcel ? absint( $parcel->section_id ) : 0;
|
||
$water_selected = $parcel ? $this->meters->get_assigned_to_parcel( $id, 'water' ) : null;
|
||
$power_selected = $parcel ? $this->meters->get_assigned_to_parcel( $id, 'power' ) : null;
|
||
$water_meters = $this->meters->get_free_by_type( 'water', $selected_section, $id );
|
||
$power_meters = $this->meters->get_free_by_type( 'power', $selected_section, $id );
|
||
$member_users = $this->assignments->get_member_users();
|
||
$member_ids = $id ? $this->assignments->get_member_ids_for_parcel( $id ) : array();
|
||
$tenant_ids = $id ? $this->assignments->get_tenant_ids_for_parcel( $id ) : array();
|
||
$tenants = $this->tenants->all_active();
|
||
?>
|
||
<div class='wrap'>
|
||
<h1><?php echo esc_html( $id ? __( 'Parzelle bearbeiten', KGVVM_TEXT_DOMAIN ) : __( 'Neue Parzelle', KGVVM_TEXT_DOMAIN ) ); ?></h1>
|
||
<?php $this->render_notice(); ?>
|
||
|
||
<?php if ( empty( $sections ) ) : ?>
|
||
<div class='notice notice-warning'><p><?php echo esc_html__( 'Bitte zuerst mindestens eine aktive Sparte anlegen.', KGVVM_TEXT_DOMAIN ); ?></p></div>
|
||
<?php endif; ?>
|
||
|
||
<form method='post'>
|
||
<?php wp_nonce_field( 'kgvvm_save_parcel' ); ?>
|
||
<input type='hidden' name='kgvvm_action' value='save_parcel' />
|
||
<input type='hidden' name='id' value='<?php echo esc_attr( $id ); ?>' />
|
||
<table class='form-table kgvvm-form-table'>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-parcel-label'><?php echo esc_html__( 'Parzellennummer / Bezeichnung', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='label' id='kgvvm-parcel-label' type='text' class='regular-text' required value='<?php echo esc_attr( $parcel ? $parcel->label : '' ); ?>' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-parcel-section'><?php echo esc_html__( 'Sparte', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<select name='section_id' id='kgvvm-parcel-section' required>
|
||
<option value=''><?php echo esc_html__( 'Bitte auswählen', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<?php foreach ( $sections as $section ) : ?>
|
||
<option value='<?php echo esc_attr( $section->id ); ?>' <?php selected( $selected_section, $section->id ); ?>><?php echo esc_html( $section->name ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Freie Wasser- und Stromzähler müssen zur gleichen Sparte gehören.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-parcel-area'><?php echo esc_html__( 'Fläche', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='area' id='kgvvm-parcel-area' type='number' min='0' step='0.01' value='<?php echo esc_attr( $parcel && null !== $parcel->area ? $parcel->area : '' ); ?>' /> <span class='kgvvm-help'>m²</span></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-parcel-rent'><?php echo esc_html__( 'Pacht pro Jahr', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<input name='annual_rent' id='kgvvm-parcel-rent' type='number' min='0' step='0.01' value='<?php echo esc_attr( $parcel && isset( $parcel->annual_rent ) && null !== $parcel->annual_rent ? $parcel->annual_rent : '' ); ?>' />
|
||
<span class='kgvvm-help'>€</span>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Optionaler jährlicher Pachtbetrag für diese Parzelle.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-parcel-status'><?php echo esc_html__( 'Status', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<select name='status' id='kgvvm-parcel-status'>
|
||
<option value='free' <?php selected( $parcel ? $parcel->status : 'free', 'free' ); ?>><?php echo esc_html__( 'Frei', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='assigned' <?php selected( $parcel ? $parcel->status : 'free', 'assigned' ); ?>><?php echo esc_html__( 'Vergeben', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='reserved' <?php selected( $parcel ? $parcel->status : 'free', 'reserved' ); ?>><?php echo esc_html__( 'Reserviert', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='inactive' <?php selected( $parcel ? $parcel->status : 'free', 'inactive' ); ?>><?php echo esc_html__( 'Inaktiv', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-water-meter'><?php echo esc_html__( 'Wasserzähler', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<select name='water_meter_id' id='kgvvm-water-meter' required>
|
||
<option value=''><?php echo esc_html__( 'Bitte auswählen', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<?php foreach ( $water_meters as $meter ) : ?>
|
||
<option value='<?php echo esc_attr( $meter->id ); ?>' <?php selected( $water_selected ? $water_selected->id : 0, $meter->id ); ?>><?php echo esc_html( $meter->meter_number . ' (Sparte #' . $meter->section_id . ')' ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Es werden nur freie oder bereits dieser Parzelle zugeordnete Wasserzähler angeboten.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-power-meter'><?php echo esc_html__( 'Stromzähler', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<select name='power_meter_id' id='kgvvm-power-meter' required>
|
||
<option value=''><?php echo esc_html__( 'Bitte auswählen', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<?php foreach ( $power_meters as $meter ) : ?>
|
||
<option value='<?php echo esc_attr( $meter->id ); ?>' <?php selected( $power_selected ? $power_selected->id : 0, $meter->id ); ?>><?php echo esc_html( $meter->meter_number . ' (Sparte #' . $meter->section_id . ')' ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Es werden nur freie oder bereits dieser Parzelle zugeordnete Stromzähler angeboten.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-members'><?php echo esc_html__( 'Mitglieder (WordPress-User)', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<select name='member_ids[]' id='kgvvm-members' multiple class='kgvvm-multiselect'>
|
||
<?php foreach ( $member_users as $member ) : ?>
|
||
<option value='<?php echo esc_attr( $member->ID ); ?>' <?php selected( in_array( (int) $member->ID, $member_ids, true ), true ); ?>><?php echo esc_html( $member->display_name . ' (' . $member->user_email . ')' ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Rolle "Mitglied" wird bei der Plugin-Aktivierung automatisch angelegt.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-tenants'><?php echo esc_html__( 'Pächter', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<select name='tenant_ids[]' id='kgvvm-tenants' multiple class='kgvvm-multiselect'>
|
||
<?php foreach ( $tenants as $tenant ) : ?>
|
||
<option value='<?php echo esc_attr( $tenant->id ); ?>' <?php selected( in_array( (int) $tenant->id, $tenant_ids, true ), true ); ?>><?php echo esc_html( $tenant->last_name . ', ' . $tenant->first_name ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Mehrfachauswahl möglich, um mehrere Pächter einer Parzelle zuzuordnen.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-parcel-note'><?php echo esc_html__( 'Bemerkung', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><textarea name='note' id='kgvvm-parcel-note' rows='5' class='large-text'><?php echo esc_textarea( $parcel ? $parcel->note : '' ); ?></textarea></td>
|
||
</tr>
|
||
</table>
|
||
<?php submit_button( $id ? __( 'Parzelle aktualisieren', KGVVM_TEXT_DOMAIN ) : __( 'Parzelle anlegen', KGVVM_TEXT_DOMAIN ) ); ?>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-parzellen' ) ); ?>' class='button-secondary'><?php echo esc_html__( 'Zurück', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
</form>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render meter page.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function render_meters_page() {
|
||
$this->require_cap( 'edit_zaehler' );
|
||
$view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'list';
|
||
|
||
if ( 'form' === $view ) {
|
||
$this->render_meter_form();
|
||
return;
|
||
}
|
||
|
||
if ( 'swap' === $view ) {
|
||
$this->render_meter_swap_form();
|
||
return;
|
||
}
|
||
|
||
$rows = $this->meters->search( $_GET );
|
||
$sections = $this->sections->all_for_options();
|
||
?>
|
||
<div class='wrap'>
|
||
<h1 class='wp-heading-inline'><?php echo esc_html__( 'Zähler', KGVVM_TEXT_DOMAIN ); ?></h1>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-zaehler', array( 'view' => 'form' ) ) ); ?>' class='page-title-action'><?php echo esc_html__( 'Neuer Zähler', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
<?php $this->render_notice(); ?>
|
||
|
||
<form method='get' class='kgvvm-toolbar'>
|
||
<input type='hidden' name='page' value='kgvvm-zaehler' />
|
||
<div class='kgvvm-filters'>
|
||
<input type='search' name='s' value='<?php echo esc_attr( isset( $_GET['s'] ) ? wp_unslash( $_GET['s'] ) : '' ); ?>' placeholder='<?php echo esc_attr__( 'Zähler suchen …', KGVVM_TEXT_DOMAIN ); ?>' />
|
||
<select name='type'>
|
||
<option value=''><?php echo esc_html__( 'Alle Typen', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='water' <?php selected( isset( $_GET['type'] ) ? wp_unslash( $_GET['type'] ) : '', 'water' ); ?>><?php echo esc_html__( 'Wasser', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='power' <?php selected( isset( $_GET['type'] ) ? wp_unslash( $_GET['type'] ) : '', 'power' ); ?>><?php echo esc_html__( 'Strom', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
</select>
|
||
<select name='section_id'>
|
||
<option value=''><?php echo esc_html__( 'Alle Sparten', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<?php foreach ( $sections as $section ) : ?>
|
||
<option value='<?php echo esc_attr( $section->id ); ?>' <?php selected( isset( $_GET['section_id'] ) ? absint( $_GET['section_id'] ) : 0, $section->id ); ?>><?php echo esc_html( $section->name ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<select name='assignment'>
|
||
<option value=''><?php echo esc_html__( 'Alle Zuordnungen', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='free' <?php selected( isset( $_GET['assignment'] ) ? wp_unslash( $_GET['assignment'] ) : '', 'free' ); ?>><?php echo esc_html__( 'Frei', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='assigned' <?php selected( isset( $_GET['assignment'] ) ? wp_unslash( $_GET['assignment'] ) : '', 'assigned' ); ?>><?php echo esc_html__( 'Zugeordnet', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='main' <?php selected( isset( $_GET['assignment'] ) ? wp_unslash( $_GET['assignment'] ) : '', 'main' ); ?>><?php echo esc_html__( 'Hauptzähler', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
</select>
|
||
<button class='button'><?php echo esc_html__( 'Filtern', KGVVM_TEXT_DOMAIN ); ?></button>
|
||
</div>
|
||
</form>
|
||
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-zaehler', 'meter_number' ) ); ?>'><?php echo esc_html__( 'Zählernummer', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-zaehler', 'type' ) ); ?>'><?php echo esc_html__( 'Typ', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><?php echo esc_html__( 'Sparte', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Parzelle', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-zaehler', 'installed_at' ) ); ?>'><?php echo esc_html__( 'Einbaudatum', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-zaehler', 'calibration_year' ) ); ?>'><?php echo esc_html__( 'Eichjahr', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><?php echo esc_html__( 'Status', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Aktionen', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php if ( empty( $rows ) ) : ?>
|
||
<tr><td colspan='8'><?php echo esc_html__( 'Noch keine Zähler vorhanden.', KGVVM_TEXT_DOMAIN ); ?></td></tr>
|
||
<?php else : ?>
|
||
<?php foreach ( $rows as $row ) : ?>
|
||
<?php $latest_swap_reading = $this->readings->get_latest_for_meter( $row->id ); ?>
|
||
<tr>
|
||
<td><strong><?php echo esc_html( $row->meter_number ); ?></strong></td>
|
||
<td><?php echo esc_html( $this->meter_type_label( $row->type ) ); ?></td>
|
||
<td><?php echo esc_html( $row->section_name ? $row->section_name : '—' ); ?></td>
|
||
<td><?php echo esc_html( $row->parcel_label ? $row->parcel_label : ( ! empty( $row->is_main_meter ) ? __( 'Hauptzähler der Sparte', KGVVM_TEXT_DOMAIN ) : __( 'frei', KGVVM_TEXT_DOMAIN ) ) ); ?></td>
|
||
<td><?php echo esc_html( $row->installed_at ? wp_date( 'd.m.Y', strtotime( $row->installed_at ) ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( ! empty( $row->calibration_year ) ? (string) $row->calibration_year : '—' ); ?></td>
|
||
<td><?php echo $this->status_badge( (int) $row->is_active === 1 ? ( ! empty( $row->parcel_id ) ? __( 'Zugeordnet', KGVVM_TEXT_DOMAIN ) : ( ! empty( $row->is_main_meter ) ? __( 'Hauptzähler', KGVVM_TEXT_DOMAIN ) : __( 'Frei', KGVVM_TEXT_DOMAIN ) ) ) : __( 'Inaktiv', KGVVM_TEXT_DOMAIN ), (int) $row->is_active === 1 ? ( ! empty( $row->parcel_id ) ? 'orange' : ( ! empty( $row->is_main_meter ) ? 'blue' : 'green' ) ) : 'gray' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></td>
|
||
<td>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-zaehler', array( 'view' => 'form', 'id' => $row->id ) ) ); ?>'><?php echo esc_html__( 'Bearbeiten', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
<?php if ( (int) $row->is_active === 1 ) : ?>
|
||
|
|
||
<button
|
||
type='button'
|
||
class='button-link kgvvm-open-swap-modal'
|
||
data-meter-id='<?php echo esc_attr( $row->id ); ?>'
|
||
data-meter-number='<?php echo esc_attr( $row->meter_number ); ?>'
|
||
data-meter-type='<?php echo esc_attr( $this->meter_type_label( $row->type ) ); ?>'
|
||
data-parcel-label='<?php echo esc_attr( $row->parcel_label ? $row->parcel_label : ( ! empty( $row->is_main_meter ) ? __( 'Hauptzähler der Sparte', KGVVM_TEXT_DOMAIN ) : __( 'frei', KGVVM_TEXT_DOMAIN ) ) ); ?>'
|
||
data-is-assigned='<?php echo esc_attr( ! empty( $row->parcel_id ) ? '1' : '0' ); ?>'
|
||
data-last-reading='<?php echo esc_attr( $latest_swap_reading ? (string) $latest_swap_reading->reading_value : '' ); ?>'
|
||
data-last-reading-display='<?php echo esc_attr( $latest_swap_reading ? $this->format_meter_value( $latest_swap_reading->reading_value, $row->type ) : '' ); ?>'
|
||
data-last-date='<?php echo esc_attr( $latest_swap_reading ? wp_date( 'd.m.Y', strtotime( $latest_swap_reading->reading_date ) ) : '' ); ?>'
|
||
data-unit='<?php echo esc_attr( $this->meter_unit_label( $row->type ) ); ?>'
|
||
><?php echo esc_html__( 'Tauschen', KGVVM_TEXT_DOMAIN ); ?></button>
|
||
<?php endif; ?>
|
||
|
|
||
<a href='<?php echo esc_url( wp_nonce_url( $this->admin_url( 'kgvvm-zaehler', array( 'kgvvm_action' => 'delete_meter', 'id' => $row->id ) ), 'kgvvm_delete_meter_' . $row->id ) ); ?>' onclick='return confirm("<?php echo esc_js( __( 'Zähler wirklich löschen?', KGVVM_TEXT_DOMAIN ) ); ?>");'><?php echo esc_html__( 'Löschen', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
|
||
<?php $this->render_meter_swap_modal(); ?>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render the swap modal directly inside the meter overview.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function render_meter_swap_modal() {
|
||
$today = current_time( 'Y-m-d' );
|
||
?>
|
||
<div id='kgvvm-swap-modal' class='kgvvm-modal' hidden>
|
||
<div class='kgvvm-modal__backdrop' data-kgvvm-close-modal></div>
|
||
<div class='kgvvm-modal__dialog' role='dialog' aria-modal='true' aria-labelledby='kgvvm-swap-modal-title'>
|
||
<button type='button' class='kgvvm-modal__close' data-kgvvm-close-modal aria-label='<?php echo esc_attr__( 'Schließen', KGVVM_TEXT_DOMAIN ); ?>'>×</button>
|
||
<h2 id='kgvvm-swap-modal-title'><?php echo esc_html__( 'Zähler tauschen', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Der Altzähler wird archiviert, die Historie bleibt erhalten und der neue Zähler übernimmt bei Bedarf direkt die Parzellenzuordnung.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
|
||
<div class='kgvvm-card kgvvm-modal__summary'>
|
||
<p><strong id='kgvvm-swap-current-meter'>—</strong></p>
|
||
<p id='kgvvm-swap-current-assignment'>—</p>
|
||
<p id='kgvvm-swap-current-reading'>—</p>
|
||
</div>
|
||
|
||
<form method='post'>
|
||
<?php wp_nonce_field( 'kgvvm_swap_meter' ); ?>
|
||
<input type='hidden' name='kgvvm_action' value='swap_meter' />
|
||
<input type='hidden' name='id' id='kgvvm-swap-meter-id' value='' />
|
||
<table class='form-table kgvvm-form-table'>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-new-meter-number'><?php echo esc_html__( 'Neue Zählernummer', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='new_meter_number' id='kgvvm-new-meter-number' type='text' class='regular-text' required value='' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-swap-date'><?php echo esc_html__( 'Tauschdatum', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='swap_date' id='kgvvm-swap-date' type='date' required value='<?php echo esc_attr( $today ); ?>' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-new-calibration-year'><?php echo esc_html__( 'Eichjahr neuer Zähler', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<input name='new_calibration_year' id='kgvvm-new-calibration-year' type='number' min='2000' max='2100' step='1' value='' />
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Optional: Jahr der Eichung des neuen Ersatz-Zählers.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tbody id='kgvvm-swap-assigned-fields'>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-old-final-reading'><?php echo esc_html__( 'Endstand alter Zähler', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<input name='old_final_reading' id='kgvvm-old-final-reading' type='number' min='0' step='0.001' value='' />
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Optionaler Abschlussstand am Tauschdatum.', KGVVM_TEXT_DOMAIN ); ?> <span id='kgvvm-swap-final-unit'></span></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-new-initial-reading'><?php echo esc_html__( 'Startstand neuer Zähler', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<input name='new_initial_reading' id='kgvvm-new-initial-reading' type='number' min='0' step='0.001' value='0' />
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Dieser Wert wird direkt als erste Ablesung gespeichert.', KGVVM_TEXT_DOMAIN ); ?> <span id='kgvvm-swap-start-unit'></span></p>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-swap-note'><?php echo esc_html__( 'Bemerkung', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<textarea name='swap_note' id='kgvvm-swap-note' rows='4' class='large-text'></textarea>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
<div class='kgvvm-modal__actions'>
|
||
<?php submit_button( __( 'Zähler tauschen', KGVVM_TEXT_DOMAIN ), 'primary', 'submit', false ); ?>
|
||
<button type='button' class='button-secondary' data-kgvvm-close-modal><?php echo esc_html__( 'Abbrechen', KGVVM_TEXT_DOMAIN ); ?></button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
const modal = document.getElementById('kgvvm-swap-modal');
|
||
if (!modal) {
|
||
return;
|
||
}
|
||
|
||
const form = modal.querySelector('form');
|
||
const meterIdInput = document.getElementById('kgvvm-swap-meter-id');
|
||
const meterTitle = document.getElementById('kgvvm-swap-current-meter');
|
||
const assignment = document.getElementById('kgvvm-swap-current-assignment');
|
||
const readingInfo = document.getElementById('kgvvm-swap-current-reading');
|
||
const assignedFields = document.getElementById('kgvvm-swap-assigned-fields');
|
||
const oldFinalInput = document.getElementById('kgvvm-old-final-reading');
|
||
const newInitialInput = document.getElementById('kgvvm-new-initial-reading');
|
||
const swapDateInput = document.getElementById('kgvvm-swap-date');
|
||
const finalUnit = document.getElementById('kgvvm-swap-final-unit');
|
||
const startUnit = document.getElementById('kgvvm-swap-start-unit');
|
||
const defaultSwapDate = <?php echo wp_json_encode( $today ); ?>;
|
||
const autoOpenMeterId = <?php echo wp_json_encode( isset( $_GET['open_swap'] ) ? absint( $_GET['id'] ) : 0 ); ?>;
|
||
|
||
function closeModal() {
|
||
modal.hidden = true;
|
||
modal.classList.remove('is-open');
|
||
form.reset();
|
||
meterIdInput.value = '';
|
||
swapDateInput.value = defaultSwapDate;
|
||
assignedFields.hidden = false;
|
||
newInitialInput.required = false;
|
||
}
|
||
|
||
document.querySelectorAll('.kgvvm-open-swap-modal').forEach(function (button) {
|
||
button.addEventListener('click', function () {
|
||
const isAssigned = button.dataset.isAssigned === '1';
|
||
meterIdInput.value = button.dataset.meterId || '';
|
||
meterTitle.textContent = (button.dataset.meterNumber || '—') + ' (' + (button.dataset.meterType || '') + ')';
|
||
assignment.textContent = isAssigned
|
||
? 'Parzelle: ' + (button.dataset.parcelLabel || '—')
|
||
: <?php echo wp_json_encode( __( 'Der Zähler ist aktuell frei und wird als freier Ersatzzähler angelegt.', KGVVM_TEXT_DOMAIN ) ); ?>;
|
||
|
||
if (button.dataset.lastReading) {
|
||
readingInfo.textContent = 'Letzte Ablesung: ' + (button.dataset.lastReadingDisplay || button.dataset.lastReading) + ' ' + (button.dataset.unit || '') + (button.dataset.lastDate ? ' am ' + button.dataset.lastDate : '');
|
||
} else {
|
||
readingInfo.textContent = <?php echo wp_json_encode( __( 'Für diesen Zähler liegt noch keine gespeicherte Ablesung vor.', KGVVM_TEXT_DOMAIN ) ); ?>;
|
||
}
|
||
|
||
finalUnit.textContent = button.dataset.unit ? '(' + button.dataset.unit + ')' : '';
|
||
startUnit.textContent = button.dataset.unit ? '(' + button.dataset.unit + ')' : '';
|
||
oldFinalInput.value = button.dataset.lastReading || '';
|
||
newInitialInput.value = '0';
|
||
newInitialInput.required = isAssigned;
|
||
assignedFields.hidden = !isAssigned;
|
||
swapDateInput.value = defaultSwapDate;
|
||
modal.hidden = false;
|
||
modal.classList.add('is-open');
|
||
});
|
||
});
|
||
|
||
modal.querySelectorAll('[data-kgvvm-close-modal]').forEach(function (closeButton) {
|
||
closeButton.addEventListener('click', closeModal);
|
||
});
|
||
|
||
document.addEventListener('keydown', function (event) {
|
||
if (event.key === 'Escape' && modal.classList.contains('is-open')) {
|
||
closeModal();
|
||
}
|
||
});
|
||
|
||
if (autoOpenMeterId) {
|
||
const autoButton = document.querySelector('.kgvvm-open-swap-modal[data-meter-id="' + autoOpenMeterId + '"]');
|
||
if (autoButton) {
|
||
autoButton.click();
|
||
}
|
||
}
|
||
});
|
||
</script>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render meter form.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function render_meter_form() {
|
||
$id = absint( isset( $_GET['id'] ) ? $_GET['id'] : 0 );
|
||
$meter = $id ? $this->meters->find( $id ) : null;
|
||
$sections = $this->sections->all_for_options();
|
||
$history = $id ? $this->readings->get_all_for_meter( $id, 50 ) : array();
|
||
?>
|
||
<div class='wrap'>
|
||
<h1><?php echo esc_html( $id ? __( 'Zähler bearbeiten', KGVVM_TEXT_DOMAIN ) : __( 'Neuer Zähler', KGVVM_TEXT_DOMAIN ) ); ?></h1>
|
||
<?php $this->render_notice(); ?>
|
||
<form method='post'>
|
||
<?php wp_nonce_field( 'kgvvm_save_meter' ); ?>
|
||
<input type='hidden' name='kgvvm_action' value='save_meter' />
|
||
<input type='hidden' name='id' value='<?php echo esc_attr( $id ); ?>' />
|
||
<table class='form-table kgvvm-form-table'>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-meter-type'><?php echo esc_html__( 'Typ', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<select name='type' id='kgvvm-meter-type'>
|
||
<option value='water' <?php selected( $meter ? $meter->type : 'water', 'water' ); ?>><?php echo esc_html__( 'Wasser', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='power' <?php selected( $meter ? $meter->type : 'water', 'power' ); ?>><?php echo esc_html__( 'Strom', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-meter-number'><?php echo esc_html__( 'Zählernummer', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='meter_number' id='kgvvm-meter-number' type='text' class='regular-text' required value='<?php echo esc_attr( $meter ? $meter->meter_number : '' ); ?>' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-meter-section'><?php echo esc_html__( 'Sparte', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<select name='section_id' id='kgvvm-meter-section' required>
|
||
<option value=''><?php echo esc_html__( 'Bitte auswählen', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<?php foreach ( $sections as $section ) : ?>
|
||
<option value='<?php echo esc_attr( $section->id ); ?>' <?php selected( $meter ? $meter->section_id : 0, $section->id ); ?>><?php echo esc_html( $section->name ); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-meter-installed'><?php echo esc_html__( 'Einbaudatum', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='installed_at' id='kgvvm-meter-installed' type='date' value='<?php echo esc_attr( $meter ? $meter->installed_at : '' ); ?>' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-meter-calibration-year'><?php echo esc_html__( 'Eichjahr', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<input name='calibration_year' id='kgvvm-meter-calibration-year' type='number' min='2000' max='2100' step='1' value='<?php echo esc_attr( $meter && ! empty( $meter->calibration_year ) ? $meter->calibration_year : '' ); ?>' />
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Für getauschte Wasser- und Stromzähler kann hier das Eichjahr hinterlegt werden. Das Jahr reicht aus.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'Aktiv', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td><label><input type='checkbox' name='is_active' value='1' <?php checked( $meter ? (int) $meter->is_active : 1, 1 ); ?> /> <?php echo esc_html__( 'Zähler ist aktiv', KGVVM_TEXT_DOMAIN ); ?></label></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-meter-note'><?php echo esc_html__( 'Bemerkung', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<textarea name='note' id='kgvvm-meter-note' rows='5' class='large-text'><?php echo esc_textarea( $meter ? $meter->note : '' ); ?></textarea>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Bei einem Zählerwechsel empfiehlt es sich, den alten Zähler inaktiv zu setzen und den neuen Zähler als eigenen Datensatz anzulegen. So bleibt die Historie nachvollziehbar.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
<?php submit_button( $id ? __( 'Zähler aktualisieren', KGVVM_TEXT_DOMAIN ) : __( 'Zähler anlegen', KGVVM_TEXT_DOMAIN ) ); ?>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-zaehler' ) ); ?>' class='button-secondary'><?php echo esc_html__( 'Zurück', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
</form>
|
||
|
||
<?php if ( $id ) : ?>
|
||
<div class='kgvvm-card' style='margin-top:24px;'>
|
||
<h2><?php echo esc_html__( 'Historie dieses Zählers', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<?php if ( empty( $history ) ) : ?>
|
||
<p><?php echo esc_html__( 'Für diesen Zähler wurden noch keine Ablesungen erfasst.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php else : ?>
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><?php echo esc_html__( 'Datum', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Parzelle', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Stand', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Flag', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Notiz', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ( $history as $entry ) : ?>
|
||
<tr>
|
||
<td><?php echo esc_html( wp_date( 'd.m.Y', strtotime( $entry->reading_date ) ) ); ?></td>
|
||
<td><?php echo esc_html( $entry->parcel_label ? $entry->parcel_label : '—' ); ?></td>
|
||
<td><?php echo esc_html( $this->format_meter_value_with_unit( $entry->reading_value, $meter->type ) ); ?></td>
|
||
<td><?php echo wp_kses_post( $this->reading_flag_badge( $entry ) ); ?></td>
|
||
<td><?php echo esc_html( $entry->note ? $entry->note : '—' ); ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render meter swap form.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function render_meter_swap_form() {
|
||
$id = absint( isset( $_GET['id'] ) ? $_GET['id'] : 0 );
|
||
$meter = $id ? $this->meters->find( $id ) : null;
|
||
$parcel = $meter && ! empty( $meter->parcel_id ) ? $this->parcels->find( (int) $meter->parcel_id ) : null;
|
||
$latest = $meter ? $this->readings->get_latest_for_meter( $meter->id ) : null;
|
||
$today = current_time( 'Y-m-d' );
|
||
$unit = $meter ? $this->meter_unit_label( $meter->type ) : '';
|
||
|
||
if ( ! $meter ) {
|
||
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', __( 'Der gewünschte Zähler wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
?>
|
||
<div class='wrap'>
|
||
<h1><?php echo esc_html__( 'Zähler tauschen', KGVVM_TEXT_DOMAIN ); ?></h1>
|
||
<?php $this->render_notice(); ?>
|
||
|
||
<div class='notice notice-info inline'>
|
||
<p>
|
||
<strong><?php echo esc_html( $meter->meter_number ); ?></strong>
|
||
(<?php echo esc_html( $this->meter_type_label( $meter->type ) ); ?>)
|
||
<?php echo esc_html( $parcel ? ' – ' . $parcel->label : ' – ' . __( 'aktuell keiner Parzelle zugeordnet', KGVVM_TEXT_DOMAIN ) ); ?>
|
||
<?php if ( $latest ) : ?>
|
||
<br /><?php echo esc_html( sprintf( __( 'Letzte Ablesung: %1$s %2$s am %3$s', KGVVM_TEXT_DOMAIN ), $this->format_meter_value( $latest->reading_value, $meter->type ), $unit, wp_date( 'd.m.Y', strtotime( $latest->reading_date ) ) ) ); ?>
|
||
<?php endif; ?>
|
||
</p>
|
||
</div>
|
||
|
||
<form method='post'>
|
||
<?php wp_nonce_field( 'kgvvm_swap_meter' ); ?>
|
||
<input type='hidden' name='kgvvm_action' value='swap_meter' />
|
||
<input type='hidden' name='id' value='<?php echo esc_attr( $id ); ?>' />
|
||
<table class='form-table kgvvm-form-table'>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-new-meter-number'><?php echo esc_html__( 'Neue Zählernummer', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='new_meter_number' id='kgvvm-new-meter-number' type='text' class='regular-text' required value='' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-swap-date'><?php echo esc_html__( 'Tauschdatum', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='swap_date' id='kgvvm-swap-date' type='date' required value='<?php echo esc_attr( $today ); ?>' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-new-calibration-year'><?php echo esc_html__( 'Eichjahr neuer Zähler', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<input name='new_calibration_year' id='kgvvm-new-calibration-year' type='number' min='2000' max='2100' step='1' value='' />
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Optional: Jahr der Eichung des neuen Ersatz-Zählers.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<?php if ( $parcel ) : ?>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-old-final-reading'><?php echo esc_html__( 'Endstand alter Zähler', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<input name='old_final_reading' id='kgvvm-old-final-reading' type='number' min='0' step='0.001' value='<?php echo esc_attr( $latest ? $latest->reading_value : '' ); ?>' />
|
||
<p class='kgvvm-help'><?php echo esc_html( sprintf( __( 'Optionaler Abschlussstand in %s am Tauschdatum.', KGVVM_TEXT_DOMAIN ), $unit ) ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-new-initial-reading'><?php echo esc_html__( 'Startstand neuer Zähler', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<input name='new_initial_reading' id='kgvvm-new-initial-reading' type='number' min='0' step='0.001' required value='0' />
|
||
<p class='kgvvm-help'><?php echo esc_html( sprintf( __( 'Dieser Stand wird direkt für die Parzelle mit %s gespeichert.', KGVVM_TEXT_DOMAIN ), $unit ) ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<?php else : ?>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'Hinweis', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td><?php echo esc_html__( 'Der alte Zähler ist aktuell keiner Parzelle zugeordnet. Der neue Zähler wird daher als freier Zähler angelegt.', KGVVM_TEXT_DOMAIN ); ?></td>
|
||
</tr>
|
||
<?php endif; ?>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-swap-note'><?php echo esc_html__( 'Bemerkung', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td>
|
||
<textarea name='swap_note' id='kgvvm-swap-note' rows='5' class='large-text'></textarea>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Der alte Zähler wird inaktiv gesetzt, die Historie bleibt erhalten und der neue Zähler übernimmt automatisch die Zuordnung.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
<?php submit_button( __( 'Zähler tauschen', KGVVM_TEXT_DOMAIN ), 'primary', 'submit', false ); ?>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-zaehler' ) ); ?>' class='button-secondary'><?php echo esc_html__( 'Abbrechen', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
</form>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render tenant page.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function render_tenants_page() {
|
||
$this->require_cap( 'edit_paechter' );
|
||
$view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'list';
|
||
|
||
if ( 'form' === $view ) {
|
||
$this->render_tenant_form();
|
||
return;
|
||
}
|
||
|
||
$rows = $this->tenants->search( $_GET );
|
||
?>
|
||
<div class='wrap'>
|
||
<h1 class='wp-heading-inline'><?php echo esc_html__( 'Pächter', KGVVM_TEXT_DOMAIN ); ?></h1>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-paechter', array( 'view' => 'form' ) ) ); ?>' class='page-title-action'><?php echo esc_html__( 'Neuer Pächter', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
<?php $this->render_notice(); ?>
|
||
|
||
<form method='get' class='kgvvm-toolbar'>
|
||
<input type='hidden' name='page' value='kgvvm-paechter' />
|
||
<div class='kgvvm-filters'>
|
||
<input type='search' name='s' value='<?php echo esc_attr( isset( $_GET['s'] ) ? wp_unslash( $_GET['s'] ) : '' ); ?>' placeholder='<?php echo esc_attr__( 'Pächter suchen …', KGVVM_TEXT_DOMAIN ); ?>' />
|
||
<select name='status'>
|
||
<option value=''><?php echo esc_html__( 'Alle Status', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='active' <?php selected( isset( $_GET['status'] ) ? wp_unslash( $_GET['status'] ) : '', 'active' ); ?>><?php echo esc_html__( 'Aktiv', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
<option value='inactive' <?php selected( isset( $_GET['status'] ) ? wp_unslash( $_GET['status'] ) : '', 'inactive' ); ?>><?php echo esc_html__( 'Inaktiv', KGVVM_TEXT_DOMAIN ); ?></option>
|
||
</select>
|
||
<button class='button'><?php echo esc_html__( 'Filtern', KGVVM_TEXT_DOMAIN ); ?></button>
|
||
</div>
|
||
</form>
|
||
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-paechter', 'last_name' ) ); ?>'><?php echo esc_html__( 'Name', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><?php echo esc_html__( 'Telefon', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'E-Mail', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><a href='<?php echo esc_url( $this->sort_url( 'kgvvm-paechter', 'contract_start' ) ); ?>'><?php echo esc_html__( 'Vertragsbeginn', KGVVM_TEXT_DOMAIN ); ?></a></th>
|
||
<th><?php echo esc_html__( 'Parzellen', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Mitglieder', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Status', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Aktionen', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php if ( empty( $rows ) ) : ?>
|
||
<tr><td colspan='8'><?php echo esc_html__( 'Noch keine Pächter vorhanden.', KGVVM_TEXT_DOMAIN ); ?></td></tr>
|
||
<?php else : ?>
|
||
<?php foreach ( $rows as $row ) : ?>
|
||
<?php $tenant_members = $this->assignments->get_members_for_tenant( $row->id ); ?>
|
||
<tr>
|
||
<td><strong><?php echo esc_html( $row->last_name . ', ' . $row->first_name ); ?></strong></td>
|
||
<td><?php echo esc_html( $row->phone ? $row->phone : '—' ); ?></td>
|
||
<td><?php echo esc_html( $row->email ? $row->email : '—' ); ?></td>
|
||
<td><?php echo esc_html( $row->contract_start ? wp_date( 'd.m.Y', strtotime( $row->contract_start ) ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( (string) $row->parcel_count ); ?></td>
|
||
<td>
|
||
<?php if ( empty( $tenant_members ) ) : ?>
|
||
—
|
||
<?php else : ?>
|
||
<?php echo esc_html( implode( ', ', array_map( array( $this, 'format_tenant_member_label' ), $tenant_members ) ) ); ?>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td><?php echo $this->status_badge( (int) $row->is_active === 1 ? __( 'Aktiv', KGVVM_TEXT_DOMAIN ) : __( 'Inaktiv', KGVVM_TEXT_DOMAIN ), (int) $row->is_active === 1 ? 'green' : 'gray' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></td>
|
||
<td>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-paechter', array( 'view' => 'form', 'id' => $row->id ) ) ); ?>'><?php echo esc_html__( 'Bearbeiten', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
|
|
||
<a href='<?php echo esc_url( wp_nonce_url( $this->admin_url( 'kgvvm-paechter', array( 'kgvvm_action' => 'delete_tenant', 'id' => $row->id ) ), 'kgvvm_delete_tenant_' . $row->id ) ); ?>' onclick='return confirm("<?php echo esc_js( __( 'Pächter wirklich löschen?', KGVVM_TEXT_DOMAIN ) ); ?>");'><?php echo esc_html__( 'Löschen', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render tenant form.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function render_tenant_form() {
|
||
$id = absint( isset( $_GET['id'] ) ? $_GET['id'] : 0 );
|
||
$tenant = $id ? $this->tenants->find( $id ) : null;
|
||
$tenant_members = $id ? $this->assignments->get_members_for_tenant( $id ) : array();
|
||
?>
|
||
<div class='wrap'>
|
||
<h1><?php echo esc_html( $id ? __( 'Pächter bearbeiten', KGVVM_TEXT_DOMAIN ) : __( 'Neuer Pächter', KGVVM_TEXT_DOMAIN ) ); ?></h1>
|
||
<?php $this->render_notice(); ?>
|
||
<form method='post'>
|
||
<?php wp_nonce_field( 'kgvvm_save_tenant' ); ?>
|
||
<input type='hidden' name='kgvvm_action' value='save_tenant' />
|
||
<input type='hidden' name='id' value='<?php echo esc_attr( $id ); ?>' />
|
||
<table class='form-table kgvvm-form-table'>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-tenant-first-name'><?php echo esc_html__( 'Vorname', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='first_name' id='kgvvm-tenant-first-name' type='text' class='regular-text' required value='<?php echo esc_attr( $tenant ? $tenant->first_name : '' ); ?>' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-tenant-last-name'><?php echo esc_html__( 'Nachname', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='last_name' id='kgvvm-tenant-last-name' type='text' class='regular-text' required value='<?php echo esc_attr( $tenant ? $tenant->last_name : '' ); ?>' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-tenant-address'><?php echo esc_html__( 'Adresse', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><textarea name='address' id='kgvvm-tenant-address' rows='4' class='large-text'><?php echo esc_textarea( $tenant ? $tenant->address : '' ); ?></textarea></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-tenant-phone'><?php echo esc_html__( 'Telefon', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='phone' id='kgvvm-tenant-phone' type='text' class='regular-text' value='<?php echo esc_attr( $tenant ? $tenant->phone : '' ); ?>' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-tenant-email'><?php echo esc_html__( 'E-Mail', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='email' id='kgvvm-tenant-email' type='email' class='regular-text' value='<?php echo esc_attr( $tenant ? $tenant->email : '' ); ?>' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-tenant-contract-start'><?php echo esc_html__( 'Vertragsbeginn', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='contract_start' id='kgvvm-tenant-contract-start' type='date' required value='<?php echo esc_attr( $tenant ? $tenant->contract_start : '' ); ?>' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-tenant-contract-end'><?php echo esc_html__( 'Vertragsende', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><input name='contract_end' id='kgvvm-tenant-contract-end' type='date' value='<?php echo esc_attr( $tenant ? $tenant->contract_end : '' ); ?>' /></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'Aktiv', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td><label><input type='checkbox' name='is_active' value='1' <?php checked( $tenant ? (int) $tenant->is_active : 1, 1 ); ?> /> <?php echo esc_html__( 'Pächter ist aktiv', KGVVM_TEXT_DOMAIN ); ?></label></td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'Zugeordnete Mitglieder', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td>
|
||
<?php if ( empty( $tenant_members ) ) : ?>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Aktuell sind über die zugeordneten Parzellen keine Mitglieder verknüpft.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php else : ?>
|
||
<ul>
|
||
<?php foreach ( $tenant_members as $member ) : ?>
|
||
<li><?php echo esc_html( $this->format_tenant_member_label( $member ) ); ?></li>
|
||
<?php endforeach; ?>
|
||
</ul>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Nur Anzeige: Die Zuordnung der Mitglieder erfolgt bei den Parzellen und ist hier nicht bearbeitbar.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php endif; ?>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><label for='kgvvm-tenant-note'><?php echo esc_html__( 'Bemerkung', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||
<td><textarea name='note' id='kgvvm-tenant-note' rows='5' class='large-text'><?php echo esc_textarea( $tenant ? $tenant->note : '' ); ?></textarea></td>
|
||
</tr>
|
||
</table>
|
||
<?php submit_button( $id ? __( 'Pächter aktualisieren', KGVVM_TEXT_DOMAIN ) : __( 'Pächter anlegen', KGVVM_TEXT_DOMAIN ) ); ?>
|
||
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-paechter' ) ); ?>' class='button-secondary'><?php echo esc_html__( 'Zurück', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
</form>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render the member page with assigned parcels and meter reading forms.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function render_my_parcels_page() {
|
||
$this->require_cap( 'view_assigned_parcels' );
|
||
|
||
$user_id = get_current_user_id();
|
||
$parcels = $this->assignments->get_parcels_for_user( $user_id );
|
||
?>
|
||
<div class='wrap'>
|
||
<div class='kgvvm-toolbar'>
|
||
<h1><?php echo esc_html__( 'Meine Parzellen', KGVVM_TEXT_DOMAIN ); ?></h1>
|
||
</div>
|
||
<?php $this->render_notice(); ?>
|
||
|
||
<?php if ( empty( $parcels ) ) : ?>
|
||
<div class='notice notice-info'><p><?php echo esc_html__( 'Ihnen ist aktuell keine Parzelle zugewiesen.', KGVVM_TEXT_DOMAIN ); ?></p></div>
|
||
<?php else : ?>
|
||
<?php foreach ( $parcels as $parcel ) : ?>
|
||
<?php
|
||
$water_meter = $this->meters->get_assigned_to_parcel( $parcel->id, 'water' );
|
||
$power_meter = $this->meters->get_assigned_to_parcel( $parcel->id, 'power' );
|
||
$water_latest = $water_meter ? $this->readings->get_latest_for_meter( $water_meter->id ) : null;
|
||
$power_latest = $power_meter ? $this->readings->get_latest_for_meter( $power_meter->id ) : null;
|
||
$recent = $this->readings->get_recent_for_parcel( $parcel->id, 8 );
|
||
?>
|
||
<div class='kgvvm-card' style='margin-bottom:20px;'>
|
||
<div class='kgvvm-toolbar'>
|
||
<h2><?php echo esc_html( $parcel->label ); ?></h2>
|
||
<a class='button button-secondary' href='<?php echo esc_url( wp_nonce_url( $this->admin_url( 'kgvvm-my-parcels', array( 'kgvvm_action' => 'export_readings_csv', 'parcel_id' => $parcel->id ) ), 'kgvvm_export_readings_' . $parcel->id ) ); ?>'><?php echo esc_html__( 'CSV-Export Ablesungen', KGVVM_TEXT_DOMAIN ); ?></a>
|
||
</div>
|
||
<p>
|
||
<strong><?php echo esc_html__( 'Sparte:', KGVVM_TEXT_DOMAIN ); ?></strong> <?php echo esc_html( $parcel->section_name ? $parcel->section_name : '—' ); ?><br />
|
||
<strong><?php echo esc_html__( 'Fläche:', KGVVM_TEXT_DOMAIN ); ?></strong> <?php echo null !== $parcel->area ? esc_html( number_format_i18n( (float) $parcel->area, 2 ) . ' m²' ) : '—'; ?><br />
|
||
<strong><?php echo esc_html__( 'Pacht:', KGVVM_TEXT_DOMAIN ); ?></strong> <?php echo isset( $parcel->annual_rent ) && null !== $parcel->annual_rent ? esc_html( $this->format_currency( $parcel->annual_rent ) ) : '—'; ?><br />
|
||
<strong><?php echo esc_html__( 'Status:', KGVVM_TEXT_DOMAIN ); ?></strong> <?php echo esc_html( $this->parcel_status_label( $parcel->status ) ); ?>
|
||
</p>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Die Parzellendaten sind schreibgeschützt. Sie können hier nur Ihre Zählerstände erfassen und exportieren.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php if ( ! empty( $parcel->note ) ) : ?>
|
||
<p><strong><?php echo esc_html__( 'Bemerkung:', KGVVM_TEXT_DOMAIN ); ?></strong> <?php echo esc_html( $parcel->note ); ?></p>
|
||
<?php endif; ?>
|
||
|
||
<div class='kgvvm-grid'>
|
||
<?php foreach ( array(
|
||
'water' => array( 'meter' => $water_meter, 'latest' => $water_latest, 'label' => __( 'Wasserzähler', KGVVM_TEXT_DOMAIN ) ),
|
||
'power' => array( 'meter' => $power_meter, 'latest' => $power_latest, 'label' => __( 'Stromzähler', KGVVM_TEXT_DOMAIN ) ),
|
||
) as $meter_config ) : ?>
|
||
<div class='kgvvm-card'>
|
||
<h3><?php echo esc_html( $meter_config['label'] ); ?></h3>
|
||
<?php if ( empty( $meter_config['meter'] ) ) : ?>
|
||
<p><?php echo esc_html__( 'Für diese Parzelle ist aktuell kein Zähler hinterlegt.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php else : ?>
|
||
<p>
|
||
<strong><?php echo esc_html__( 'Zählernummer:', KGVVM_TEXT_DOMAIN ); ?></strong> <?php echo esc_html( $meter_config['meter']->meter_number ); ?><br />
|
||
<strong><?php echo esc_html__( 'Letzte Ablesung:', KGVVM_TEXT_DOMAIN ); ?></strong>
|
||
<?php if ( $meter_config['latest'] ) : ?>
|
||
<?php echo esc_html( $this->format_meter_value_with_unit( $meter_config['latest']->reading_value, $meter_config['meter']->type ) . ' am ' . wp_date( 'd.m.Y', strtotime( $meter_config['latest']->reading_date ) ) ); ?>
|
||
<?php else : ?>
|
||
<?php echo esc_html__( 'Noch keine Ablesung vorhanden', KGVVM_TEXT_DOMAIN ); ?>
|
||
<?php endif; ?>
|
||
</p>
|
||
<form method='post'>
|
||
<?php wp_nonce_field( 'kgvvm_save_meter_reading' ); ?>
|
||
<input type='hidden' name='kgvvm_action' value='save_meter_reading' />
|
||
<input type='hidden' name='parcel_id' value='<?php echo esc_attr( $parcel->id ); ?>' />
|
||
<input type='hidden' name='meter_id' value='<?php echo esc_attr( $meter_config['meter']->id ); ?>' />
|
||
<p>
|
||
<label><?php echo esc_html__( 'Ablesedatum', KGVVM_TEXT_DOMAIN ); ?><br />
|
||
<input type='date' name='reading_date' required value='<?php echo esc_attr( wp_date( 'Y-m-d' ) ); ?>' />
|
||
</label>
|
||
</p>
|
||
<p>
|
||
<label><?php echo esc_html__( 'Zählerstand', KGVVM_TEXT_DOMAIN ); ?> (<?php echo esc_html( $this->meter_unit_label( $meter_config['meter']->type ) ); ?>)<br />
|
||
<input type='number' min='0' step='0.001' name='reading_value' required />
|
||
</label>
|
||
</p>
|
||
<p>
|
||
<label><?php echo esc_html__( 'Notiz', KGVVM_TEXT_DOMAIN ); ?><br />
|
||
<textarea name='note' rows='3' class='large-text'></textarea>
|
||
</label>
|
||
</p>
|
||
<?php submit_button( __( 'Ablesung speichern', KGVVM_TEXT_DOMAIN ), 'secondary', 'submit', false ); ?>
|
||
</form>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<div class='kgvvm-card'>
|
||
<h3><?php echo esc_html__( 'Letzte Einträge', KGVVM_TEXT_DOMAIN ); ?></h3>
|
||
<?php if ( empty( $recent ) ) : ?>
|
||
<p><?php echo esc_html__( 'Noch keine Ablesungen für diese Parzelle erfasst.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php else : ?>
|
||
<table class='widefat striped'>
|
||
<thead>
|
||
<tr>
|
||
<th><?php echo esc_html__( 'Datum', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Typ', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Zähler', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Stand', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Flag', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ( $recent as $reading ) : ?>
|
||
<tr>
|
||
<td><?php echo esc_html( wp_date( 'd.m.Y', strtotime( $reading->reading_date ) ) ); ?></td>
|
||
<td><?php echo esc_html( $this->meter_type_label( $reading->type ) ); ?></td>
|
||
<td><?php echo esc_html( $reading->meter_number ); ?></td>
|
||
<td><?php echo esc_html( $this->format_meter_value_with_unit( $reading->reading_value, $reading->type ) ); ?></td>
|
||
<td><?php echo wp_kses_post( $this->reading_flag_badge( $reading ) ); ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render the shared club chat for members and board.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function render_chat_page() {
|
||
$this->require_chat_access();
|
||
|
||
$rooms = $this->get_chat_rooms();
|
||
$requested = isset( $_GET['room'] ) ? sanitize_key( wp_unslash( $_GET['room'] ) ) : '';
|
||
$room_keys = array_keys( $rooms );
|
||
$current_room = ( $requested && isset( $rooms[ $requested ] ) ) ? $requested : reset( $room_keys );
|
||
$messages = $this->normalize_chat_messages( $this->chat->get_recent_messages( $current_room, 60 ) );
|
||
$last_message = ! empty( $messages ) ? end( $messages ) : null;
|
||
|
||
if ( ! empty( $messages ) ) {
|
||
reset( $messages );
|
||
}
|
||
?>
|
||
<div class='wrap'>
|
||
<h1><?php echo esc_html__( 'Vereinschat', KGVVM_TEXT_DOMAIN ); ?></h1>
|
||
<?php $this->render_notice(); ?>
|
||
|
||
<div class='kgvvm-card'>
|
||
<p><?php echo esc_html__( 'Hier können Mitglieder, Vorstand und Verwaltung direkt miteinander schreiben. Mitglieder sehen nur den Vereinschat sowie die Sparte ihrer zugewiesenen Parzelle. Der Raum „Vorstand“ ist nur für berechtigte Rollen sichtbar.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</div>
|
||
|
||
<div class='kgvvm-chat-app' data-room='<?php echo esc_attr( $current_room ); ?>' data-current-user-id='<?php echo esc_attr( get_current_user_id() ); ?>'>
|
||
<aside class='kgvvm-chat-sidebar kgvvm-card'>
|
||
<h2><?php echo esc_html__( 'Räume', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||
<div class='kgvvm-chat-room-list'>
|
||
<?php foreach ( $rooms as $room_key => $room ) : ?>
|
||
<button
|
||
type='button'
|
||
class='button button-secondary kgvvm-chat-room<?php echo $current_room === $room_key ? ' is-active' : ''; ?>'
|
||
data-chat-room='<?php echo esc_attr( $room_key ); ?>'
|
||
data-chat-label='<?php echo esc_attr( $room['label'] ); ?>'
|
||
data-chat-description='<?php echo esc_attr( $room['description'] ); ?>'
|
||
>
|
||
<?php echo esc_html( $room['label'] ); ?>
|
||
</button>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</aside>
|
||
|
||
<section class='kgvvm-chat-panel kgvvm-card'>
|
||
<div class='kgvvm-chat-panel__header'>
|
||
<div>
|
||
<h2 data-chat-room-title><?php echo esc_html( $rooms[ $current_room ]['label'] ); ?></h2>
|
||
<p class='kgvvm-help' data-chat-room-description><?php echo esc_html( $rooms[ $current_room ]['description'] ); ?></p>
|
||
</div>
|
||
<span class='kgvvm-status kgvvm-status--blue'><?php echo esc_html__( 'nur intern', KGVVM_TEXT_DOMAIN ); ?></span>
|
||
</div>
|
||
|
||
<div class='kgvvm-chat-messages' data-chat-messages data-last-id='<?php echo esc_attr( $last_message ? $last_message['id'] : 0 ); ?>'>
|
||
<?php if ( empty( $messages ) ) : ?>
|
||
<p class='kgvvm-chat-empty'><?php echo esc_html__( 'Noch keine Nachrichten in diesem Raum.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
<?php else : ?>
|
||
<?php foreach ( $messages as $message ) : ?>
|
||
<article class='kgvvm-chat-message<?php echo ! empty( $message['is_own'] ) ? ' kgvvm-chat-message--own' : ''; ?>' data-id='<?php echo esc_attr( $message['id'] ); ?>'>
|
||
<div class='kgvvm-chat-message__meta'>
|
||
<strong><?php echo esc_html( $message['user'] ); ?></strong>
|
||
<?php if ( ! empty( $message['role'] ) ) : ?>
|
||
· <?php echo esc_html( $message['role'] ); ?>
|
||
<?php endif; ?>
|
||
· <?php echo esc_html( $message['time'] ); ?>
|
||
</div>
|
||
<div class='kgvvm-chat-message__body'><?php echo nl2br( esc_html( $message['message'] ) ); ?></div>
|
||
</article>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<form class='kgvvm-chat-form' data-chat-form>
|
||
<label for='kgvvm-chat-input' class='screen-reader-text'><?php echo esc_html__( 'Nachricht', KGVVM_TEXT_DOMAIN ); ?></label>
|
||
<textarea id='kgvvm-chat-input' rows='3' maxlength='1000' data-chat-input placeholder='<?php echo esc_attr__( 'Nachricht an den Verein schreiben …', KGVVM_TEXT_DOMAIN ); ?>'></textarea>
|
||
<div class='kgvvm-chat-form__actions'>
|
||
<span class='kgvvm-help' data-chat-status></span>
|
||
<button type='submit' class='button button-primary'><?php echo esc_html__( 'Senden', KGVVM_TEXT_DOMAIN ); ?></button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render settings page.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function render_settings_page() {
|
||
$this->require_cap( Roles::SETTINGS_CAP );
|
||
$settings = wp_parse_args( get_option( 'kgvvm_settings', array() ), $this->get_settings_defaults() );
|
||
?>
|
||
<div class='wrap'>
|
||
<h1><?php echo esc_html__( 'Einstellungen', KGVVM_TEXT_DOMAIN ); ?></h1>
|
||
<?php $this->render_notice(); ?>
|
||
<form method='post'>
|
||
<?php wp_nonce_field( 'kgvvm_save_settings' ); ?>
|
||
<input type='hidden' name='kgvvm_action' value='save_settings' />
|
||
<table class='form-table kgvvm-form-table'>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'Mitgliederregel', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td>
|
||
<label>
|
||
<input type='checkbox' name='allow_multiple_member_parcels' value='1' <?php checked( ! empty( $settings['allow_multiple_member_parcels'] ), true ); ?> />
|
||
<?php echo esc_html__( 'Ein Mitglied darf mehreren Parzellen zugeordnet werden.', KGVVM_TEXT_DOMAIN ); ?>
|
||
</label>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Wenn diese Option deaktiviert ist, wird eine Mehrfachzuordnung bei der Parzellenpflege validiert und blockiert.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'Plausibilitätsgrenze Wasser', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td>
|
||
<input type='number' min='0' step='0.001' name='water_usage_alert_threshold' value='<?php echo esc_attr( $settings['water_usage_alert_threshold'] ); ?>' />
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Wenn der neue Wasserzählerstand den letzten Eintrag um mehr als diesen Wert in m³ erhöht, wird nach dem Speichern eine Warnung angezeigt.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'Strom-Einheit', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td>
|
||
<select name='power_unit'>
|
||
<option value='kwh' <?php selected( $settings['power_unit'], 'kwh' ); ?>>kWh</option>
|
||
<option value='mwh' <?php selected( $settings['power_unit'], 'mwh' ); ?>>MWh</option>
|
||
</select>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Wasser wird immer in m³ geführt. Für Strom kann hier die gewünschte Einheit gewählt werden.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'Plausibilitätsgrenze Strom', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td>
|
||
<input type='number' min='0' step='0.001' name='power_usage_alert_threshold' value='<?php echo esc_attr( $settings['power_usage_alert_threshold'] ); ?>' />
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Wenn der neue Stromzählerstand den letzten Eintrag um mehr als diesen Wert in der gewählten Strom-Einheit erhöht, wird nach dem Speichern eine Warnung angezeigt.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'PDF / Briefkopf', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Diese Angaben erscheinen in der Druckansicht und im TCPDF-Export der Jahresabrechnung.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'Vereinsname', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td>
|
||
<input type='text' name='pdf_club_name' class='regular-text' value='<?php echo esc_attr( $settings['pdf_club_name'] ); ?>' />
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Wird als Überschrift im PDF- und Druckkopf verwendet.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'Logo-URL', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td>
|
||
<input type='url' name='pdf_logo_url' class='regular-text code' value='<?php echo esc_attr( $settings['pdf_logo_url'] ); ?>' placeholder='https://example.de/logo.png' />
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Optional: URL zu einem Vereinslogo aus der Mediathek oder einer öffentlich erreichbaren Bilddatei.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'Adress- / Kontaktblock', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td>
|
||
<textarea name='pdf_contact_block' rows='4' class='large-text'><?php echo esc_textarea( $settings['pdf_contact_block'] ); ?></textarea>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Mehrzeilig möglich, z. B. Adresse, Telefon, E-Mail oder Bankverbindung.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'Einleitungstext', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td>
|
||
<textarea name='pdf_intro_text' rows='3' class='large-text'><?php echo esc_textarea( $settings['pdf_intro_text'] ); ?></textarea>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Optionaler Hinweistext oberhalb der Abrechnungsdetails.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope='row'><?php echo esc_html__( 'Fußtext / Hinweis', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<td>
|
||
<textarea name='pdf_footer_text' rows='3' class='large-text'><?php echo esc_textarea( $settings['pdf_footer_text'] ); ?></textarea>
|
||
<p class='kgvvm-help'><?php echo esc_html__( 'Erscheint unterhalb der Jahresabrechnung, z. B. als Zahlungs- oder Rückfragehinweis.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
<?php submit_button( __( 'Einstellungen speichern', KGVVM_TEXT_DOMAIN ) ); ?>
|
||
</form>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Handle AJAX polling for chat messages.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function ajax_fetch_chat_messages() {
|
||
$this->require_chat_access();
|
||
check_ajax_referer( 'kgvvm_chat_nonce', 'nonce' );
|
||
|
||
$room_key = isset( $_POST['room'] ) ? sanitize_key( wp_unslash( $_POST['room'] ) ) : 'general';
|
||
$after_id = absint( isset( $_POST['after_id'] ) ? wp_unslash( $_POST['after_id'] ) : 0 );
|
||
|
||
if ( ! $this->is_chat_room_allowed( $room_key ) ) {
|
||
wp_send_json_error( array( 'message' => __( 'Sie dürfen diesen Chatraum nicht öffnen.', KGVVM_TEXT_DOMAIN ) ), 403 );
|
||
}
|
||
|
||
wp_send_json_success(
|
||
array(
|
||
'messages' => $this->normalize_chat_messages( $this->chat->get_recent_messages( $room_key, $after_id > 0 ? 100 : 60, $after_id ) ),
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Handle AJAX submission for one new chat message.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function ajax_send_chat_message() {
|
||
$this->require_chat_access();
|
||
check_ajax_referer( 'kgvvm_chat_nonce', 'nonce' );
|
||
|
||
$room_key = isset( $_POST['room'] ) ? sanitize_key( wp_unslash( $_POST['room'] ) ) : 'general';
|
||
$message = isset( $_POST['message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['message'] ) ) : '';
|
||
$message = trim( substr( $message, 0, 1000 ) );
|
||
|
||
if ( ! $this->is_chat_room_allowed( $room_key ) ) {
|
||
wp_send_json_error( array( 'message' => __( 'Sie dürfen in diesem Chatraum nicht schreiben.', KGVVM_TEXT_DOMAIN ) ), 403 );
|
||
}
|
||
|
||
if ( '' === $message ) {
|
||
wp_send_json_error( array( 'message' => __( 'Bitte zuerst eine Nachricht eingeben.', KGVVM_TEXT_DOMAIN ) ), 400 );
|
||
}
|
||
|
||
$created = $this->chat->save_message( $room_key, get_current_user_id(), $message );
|
||
|
||
if ( ! $created ) {
|
||
wp_send_json_error( array( 'message' => __( 'Die Nachricht konnte nicht gespeichert werden.', KGVVM_TEXT_DOMAIN ) ), 500 );
|
||
}
|
||
|
||
wp_send_json_success(
|
||
array(
|
||
'messages' => $this->normalize_chat_messages( array( $created ) ),
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Return plugin settings defaults.
|
||
*
|
||
* @return array
|
||
*/
|
||
private function get_settings_defaults() {
|
||
return array(
|
||
'allow_multiple_member_parcels' => 1,
|
||
'water_usage_alert_threshold' => 25,
|
||
'power_usage_alert_threshold' => 1000,
|
||
'power_unit' => 'kwh',
|
||
'pdf_club_name' => get_bloginfo( 'name' ),
|
||
'pdf_logo_url' => '',
|
||
'pdf_contact_block' => '',
|
||
'pdf_intro_text' => __( 'Diese Jahresabrechnung wurde automatisch mit der KGV Vereinsverwaltung erstellt.', KGVVM_TEXT_DOMAIN ),
|
||
'pdf_footer_text' => __( 'Bitte prüfen Sie die Angaben und melden Sie Rückfragen an den Vorstand.', KGVVM_TEXT_DOMAIN ),
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Export one parcel's reading history as CSV.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function export_readings_csv() {
|
||
$this->require_cap( 'view_assigned_parcels' );
|
||
|
||
$parcel_id = absint( isset( $_GET['parcel_id'] ) ? $_GET['parcel_id'] : 0 );
|
||
$nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
|
||
|
||
if ( ! $parcel_id || ! wp_verify_nonce( $nonce, 'kgvvm_export_readings_' . $parcel_id ) ) {
|
||
$this->redirect_with_notice( 'kgvvm-my-parcels', 'error', __( 'Der CSV-Export wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
if ( ! current_user_can( 'manage_kleingarten' ) && ! $this->assignments->user_has_parcel( get_current_user_id(), $parcel_id ) ) {
|
||
wp_die( esc_html__( 'Sie dürfen nur die Ablesungen Ihrer eigenen Parzellen exportieren.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
$parcel = $this->parcels->find( $parcel_id );
|
||
|
||
if ( ! $parcel ) {
|
||
$this->redirect_with_notice( 'kgvvm-my-parcels', 'error', __( 'Die gewünschte Parzelle wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
|
||
$rows = $this->readings->get_all_for_parcel( $parcel_id );
|
||
|
||
nocache_headers();
|
||
header( 'Content-Type: text/csv; charset=utf-8' );
|
||
header( 'Content-Disposition: attachment; filename=ablesungen-' . sanitize_file_name( $parcel->label ) . '-' . gmdate( 'Ymd-His' ) . '.csv' );
|
||
|
||
$output = fopen( 'php://output', 'w' );
|
||
|
||
if ( false === $output ) {
|
||
exit;
|
||
}
|
||
|
||
fwrite( $output, "\xEF\xBB\xBF" );
|
||
fputcsv( $output, array( 'Parzelle', 'Datum', 'Typ', 'Zählernummer', 'Zählerstand', 'Einheit', 'Flag', 'Notiz' ), ';' );
|
||
|
||
foreach ( $rows as $row ) {
|
||
fputcsv(
|
||
$output,
|
||
array(
|
||
$parcel->label,
|
||
wp_date( 'd.m.Y', strtotime( $row->reading_date ) ),
|
||
$this->meter_type_label( $row->type ),
|
||
$row->meter_number,
|
||
$this->format_meter_value( $row->reading_value, $row->type ),
|
||
$this->meter_unit_label( $row->type ),
|
||
! empty( $row->is_self_reading ) ? __( 'Selbstablesung', KGVVM_TEXT_DOMAIN ) : '',
|
||
$row->note,
|
||
),
|
||
';'
|
||
);
|
||
}
|
||
|
||
fclose( $output );
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* Ensure the current user may access the chat.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function require_chat_access() {
|
||
if ( ! current_user_can( 'view_assigned_parcels' ) && ! current_user_can( 'manage_kleingarten' ) ) {
|
||
wp_die( esc_html__( 'Sie haben keine Berechtigung für den Vereinschat.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
}
|
||
|
||
/**
|
||
* List all chat rooms visible for the current user.
|
||
*
|
||
* @return array
|
||
*/
|
||
private function get_chat_rooms() {
|
||
$rooms = array(
|
||
'general' => array(
|
||
'label' => __( 'Vereinschat', KGVVM_TEXT_DOMAIN ),
|
||
'description' => __( 'Offener Austausch für alle Mitglieder, Vorstand und Verwaltung.', KGVVM_TEXT_DOMAIN ),
|
||
),
|
||
);
|
||
|
||
$is_management = current_user_can( Roles::SETTINGS_CAP ) || current_user_can( 'manage_kleingarten' );
|
||
|
||
if ( $is_management ) {
|
||
$rooms['board'] = array(
|
||
'label' => __( 'Vorstand', KGVVM_TEXT_DOMAIN ),
|
||
'description' => __( 'Interner Raum für Vorstand und Verwaltung.', KGVVM_TEXT_DOMAIN ),
|
||
);
|
||
}
|
||
|
||
$sections = array();
|
||
|
||
if ( $is_management ) {
|
||
$sections = $this->sections->all_for_options();
|
||
} else {
|
||
$user_parcels = $this->assignments->get_parcels_for_user( get_current_user_id() );
|
||
$seen_sections = array();
|
||
|
||
foreach ( (array) $user_parcels as $parcel ) {
|
||
$section_id = ! empty( $parcel->section_id ) ? (int) $parcel->section_id : 0;
|
||
|
||
if ( $section_id > 0 && ! isset( $seen_sections[ $section_id ] ) ) {
|
||
$section = $this->sections->find( $section_id );
|
||
|
||
if ( $section ) {
|
||
$sections[] = $section;
|
||
}
|
||
|
||
$seen_sections[ $section_id ] = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
foreach ( (array) $sections as $section ) {
|
||
$section_id = ! empty( $section->id ) ? (int) $section->id : 0;
|
||
$section_name = ! empty( $section->name ) ? $section->name : '';
|
||
|
||
if ( $section_id < 1 ) {
|
||
continue;
|
||
}
|
||
|
||
$section_room_key = 'section-' . $section_id;
|
||
$rooms[ $section_room_key ] = array(
|
||
'label' => sprintf( __( 'Sparte: %s', KGVVM_TEXT_DOMAIN ), $section_name ? $section_name : $section_id ),
|
||
'description' => sprintf( __( 'Austausch für Mitglieder der Sparte %s.', KGVVM_TEXT_DOMAIN ), $section_name ? $section_name : $section_id ),
|
||
);
|
||
}
|
||
|
||
return $rooms;
|
||
}
|
||
|
||
/**
|
||
* Check whether a chat room is available for the current user.
|
||
*
|
||
* @param string $room_key Room key.
|
||
* @return bool
|
||
*/
|
||
private function is_chat_room_allowed( $room_key ) {
|
||
$rooms = $this->get_chat_rooms();
|
||
return isset( $rooms[ sanitize_key( $room_key ) ] );
|
||
}
|
||
|
||
/**
|
||
* Normalize chat messages for HTML and AJAX responses.
|
||
*
|
||
* @param array $messages Raw database rows.
|
||
* @return array
|
||
*/
|
||
private function normalize_chat_messages( $messages ) {
|
||
$current_user_id = get_current_user_id();
|
||
$normalized = array();
|
||
|
||
foreach ( (array) $messages as $message ) {
|
||
$normalized[] = array(
|
||
'id' => isset( $message->id ) ? (int) $message->id : 0,
|
||
'user_id' => isset( $message->user_id ) ? (int) $message->user_id : 0,
|
||
'user' => ! empty( $message->display_name ) ? (string) $message->display_name : __( 'Mitglied', KGVVM_TEXT_DOMAIN ),
|
||
'role' => $this->get_chat_role_label( isset( $message->user_id ) ? (int) $message->user_id : 0 ),
|
||
'message' => isset( $message->message ) ? (string) $message->message : '',
|
||
'time' => ! empty( $message->created_at ) ? wp_date( 'd.m.Y H:i', strtotime( $message->created_at ) ) : '',
|
||
'is_own' => isset( $message->user_id ) && (int) $message->user_id === (int) $current_user_id,
|
||
);
|
||
}
|
||
|
||
return $normalized;
|
||
}
|
||
|
||
/**
|
||
* Resolve a readable sender label.
|
||
*
|
||
* @param int $user_id Sender ID.
|
||
* @return string
|
||
*/
|
||
private function get_chat_role_label( $user_id ) {
|
||
$user_id = absint( $user_id );
|
||
|
||
if ( $user_id > 0 && ( user_can( $user_id, Roles::SETTINGS_CAP ) || user_can( $user_id, 'manage_kleingarten' ) ) ) {
|
||
return __( 'Vorstand / Verwaltung', KGVVM_TEXT_DOMAIN );
|
||
}
|
||
|
||
return __( 'Mitglied', KGVVM_TEXT_DOMAIN );
|
||
}
|
||
|
||
/**
|
||
* Return a visible badge for self-readings.
|
||
*
|
||
* @param object $reading Reading row.
|
||
* @return string
|
||
*/
|
||
private function reading_flag_badge( $reading ) {
|
||
if ( empty( $reading ) || empty( $reading->is_self_reading ) ) {
|
||
return '—';
|
||
}
|
||
|
||
return $this->status_badge( __( 'Selbstablesung', KGVVM_TEXT_DOMAIN ), 'blue' );
|
||
}
|
||
|
||
/**
|
||
* Build a plausibility warning for unusual jumps.
|
||
*
|
||
* @param object $meter Meter object.
|
||
* @param object|null $latest Latest previous reading.
|
||
* @param float $new_reading New reading value.
|
||
* @return string
|
||
*/
|
||
private function build_meter_jump_warning_message( $meter, $latest, $new_reading ) {
|
||
if ( ! $meter || ! $latest ) {
|
||
return '';
|
||
}
|
||
|
||
$settings = wp_parse_args( get_option( 'kgvvm_settings', array() ), $this->get_settings_defaults() );
|
||
$threshold = 'water' === $meter->type ? (float) $settings['water_usage_alert_threshold'] : (float) $settings['power_usage_alert_threshold'];
|
||
|
||
if ( $threshold <= 0 ) {
|
||
return '';
|
||
}
|
||
|
||
$delta = (float) $new_reading - (float) $latest->reading_value;
|
||
|
||
if ( $delta <= $threshold ) {
|
||
return '';
|
||
}
|
||
|
||
return sprintf(
|
||
/* translators: 1: meter number, 2: consumption increase */
|
||
__( 'Auffälliger Verbrauchssprung beim Zähler %1$s: +%2$s seit der letzten Ablesung. Bitte prüfen.', KGVVM_TEXT_DOMAIN ),
|
||
$meter->meter_number,
|
||
$this->format_meter_value_with_unit( $delta, $meter->type )
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Format one meter value for display.
|
||
*
|
||
* @param float|int|string $value Numeric value.
|
||
* @param string $type Meter type.
|
||
* @return string
|
||
*/
|
||
private function format_meter_value( $value, $type ) {
|
||
$number = (float) $value;
|
||
|
||
if ( 'water' === $type ) {
|
||
return number_format( $number, 2, ',', '' );
|
||
}
|
||
|
||
return number_format( $number, 0, '', '' );
|
||
}
|
||
|
||
/**
|
||
* Format one meter value together with its unit.
|
||
*
|
||
* @param float|int|string $value Numeric value.
|
||
* @param string $type Meter type.
|
||
* @return string
|
||
*/
|
||
private function format_meter_value_with_unit( $value, $type ) {
|
||
return $this->format_meter_value( $value, $type ) . ' ' . $this->meter_unit_label( $type );
|
||
}
|
||
|
||
/**
|
||
* Format a currency value for display.
|
||
*
|
||
* @param float|int|string $value Numeric value.
|
||
* @return string
|
||
*/
|
||
private function format_currency( $value ) {
|
||
return number_format_i18n( (float) $value, 2 ) . ' €';
|
||
}
|
||
|
||
/**
|
||
* Format a yearly unit price for display.
|
||
*
|
||
* @param float|int|string $value Numeric value.
|
||
* @param string $unit Unit label.
|
||
* @return string
|
||
*/
|
||
private function format_price_per_unit( $value, $unit ) {
|
||
return number_format_i18n( (float) $value, 4 ) . ' € / ' . $unit;
|
||
}
|
||
|
||
/**
|
||
* Normalize the saved distribution type for one cost entry.
|
||
*
|
||
* @param object|array $entry Cost entry.
|
||
* @return string
|
||
*/
|
||
private function get_cost_distribution_type( $entry ) {
|
||
$type = 'parcel';
|
||
|
||
if ( is_object( $entry ) && isset( $entry->distribution_type ) ) {
|
||
$type = sanitize_key( (string) $entry->distribution_type );
|
||
} elseif ( is_array( $entry ) && isset( $entry['distribution_type'] ) ) {
|
||
$type = sanitize_key( (string) $entry['distribution_type'] );
|
||
}
|
||
|
||
return in_array( $type, array( 'parcel', 'member' ), true ) ? $type : 'parcel';
|
||
}
|
||
|
||
/**
|
||
* Return the UI label for one cost distribution type.
|
||
*
|
||
* @param string $distribution_type Distribution type.
|
||
* @return string
|
||
*/
|
||
private function get_cost_distribution_label( $distribution_type ) {
|
||
return 'member' === $distribution_type ? __( 'Mitglied', KGVVM_TEXT_DOMAIN ) : __( 'Parzelle', KGVVM_TEXT_DOMAIN );
|
||
}
|
||
|
||
/**
|
||
* Resolve the amount per parcel/member for one cost entry.
|
||
*
|
||
* @param object|array $entry Cost entry.
|
||
* @param int $parcel_count Active parcel count.
|
||
* @param int $member_count Active member count.
|
||
* @return float
|
||
*/
|
||
private function get_cost_unit_amount( $entry, $parcel_count = 0, $member_count = 0 ) {
|
||
if ( is_object( $entry ) && isset( $entry->unit_amount ) && '' !== trim( (string) $entry->unit_amount ) ) {
|
||
return (float) $entry->unit_amount;
|
||
}
|
||
|
||
if ( is_array( $entry ) && isset( $entry['unit_amount'] ) && '' !== trim( (string) $entry['unit_amount'] ) ) {
|
||
return (float) $entry['unit_amount'];
|
||
}
|
||
|
||
$distribution_type = $this->get_cost_distribution_type( $entry );
|
||
$total_cost = 0.0;
|
||
|
||
if ( is_object( $entry ) && isset( $entry->total_cost ) ) {
|
||
$total_cost = (float) $entry->total_cost;
|
||
} elseif ( is_array( $entry ) && isset( $entry['total_cost'] ) ) {
|
||
$total_cost = (float) $entry['total_cost'];
|
||
}
|
||
|
||
$divisor = 'member' === $distribution_type ? max( 1, (int) $member_count ) : max( 1, (int) $parcel_count );
|
||
|
||
return $divisor > 0 ? $total_cost / $divisor : 0.0;
|
||
}
|
||
|
||
/**
|
||
* Calculate the overall yearly total for one cost entry.
|
||
*
|
||
* @param object|array $entry Cost entry.
|
||
* @param int $parcel_count Active parcel count.
|
||
* @param int $member_count Active member count.
|
||
* @return float
|
||
*/
|
||
private function get_cost_total_amount( $entry, $parcel_count = 0, $member_count = 0 ) {
|
||
$distribution_type = $this->get_cost_distribution_type( $entry );
|
||
$unit_amount = $this->get_cost_unit_amount( $entry, $parcel_count, $member_count );
|
||
$multiplier = 'member' === $distribution_type ? max( 0, (int) $member_count ) : max( 0, (int) $parcel_count );
|
||
|
||
if ( $multiplier < 1 ) {
|
||
if ( is_object( $entry ) && isset( $entry->total_cost ) ) {
|
||
return (float) $entry->total_cost;
|
||
}
|
||
|
||
if ( is_array( $entry ) && isset( $entry['total_cost'] ) ) {
|
||
return (float) $entry['total_cost'];
|
||
}
|
||
}
|
||
|
||
return round( $unit_amount * $multiplier, 2 );
|
||
}
|
||
|
||
/**
|
||
* Filter statement utility rows by meter type.
|
||
*
|
||
* @param array $rows Utility rows.
|
||
* @param string $type Meter type.
|
||
* @return array
|
||
*/
|
||
private function get_statement_rows_by_meter_type( $rows, $type ) {
|
||
return array_values(
|
||
array_filter(
|
||
(array) $rows,
|
||
static function( $row ) use ( $type ) {
|
||
return isset( $row->type ) && $type === $row->type;
|
||
}
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Build the detailed meter table for HTML/PDF annual statements.
|
||
*
|
||
* @param array $rows Statement rows.
|
||
* @param string $type Meter type.
|
||
* @param bool $is_pdf Whether PDF markup is requested.
|
||
* @return string
|
||
*/
|
||
private function build_statement_meter_detail_table( $rows, $type, $is_pdf = false ) {
|
||
$rows = array_values( (array) $rows );
|
||
$unit = 'power' === $type ? 'kWh' : 'm³';
|
||
$empty_msg = 'power' === $type ? __( 'Keine auswertbaren Stromdaten vorhanden.', KGVVM_TEXT_DOMAIN ) : __( 'Keine auswertbaren Wasserdaten vorhanden.', KGVVM_TEXT_DOMAIN );
|
||
|
||
ob_start();
|
||
|
||
if ( empty( $rows ) ) {
|
||
echo '<p>' . esc_html( $empty_msg ) . '</p>';
|
||
return (string) ob_get_clean();
|
||
}
|
||
?>
|
||
<?php if ( $is_pdf ) : ?>
|
||
<table cellpadding="5" cellspacing="0" border="1">
|
||
<?php else : ?>
|
||
<table class='widefat striped'>
|
||
<?php endif; ?>
|
||
<?php if ( ! $is_pdf ) : ?>
|
||
<thead>
|
||
<?php endif; ?>
|
||
<tr>
|
||
<th><?php echo esc_html__( 'Seriennummer', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Stand alt', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Stand neu', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Verbrauch', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Einzelpreis', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Gesamtpreis', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
<th><?php echo esc_html__( 'Schwund', KGVVM_TEXT_DOMAIN ); ?></th>
|
||
</tr>
|
||
<?php if ( ! $is_pdf ) : ?>
|
||
</thead>
|
||
<tbody>
|
||
<?php endif; ?>
|
||
<?php foreach ( $rows as $row ) : ?>
|
||
<tr>
|
||
<td><?php echo esc_html( ! empty( $row->meter_number ) ? $row->meter_number : '—' ); ?></td>
|
||
<td><?php echo esc_html( null !== $row->previous_value ? $this->format_meter_value_with_unit( $row->previous_value, $type ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( null !== $row->reading_value ? $this->format_meter_value_with_unit( $row->reading_value, $type ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( null !== $row->consumption ? $this->format_meter_value_with_unit( $row->consumption, $type ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( null !== $row->unit_price ? $this->format_price_per_unit( $row->unit_price, $unit ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( null !== $row->calculated_cost ? $this->format_currency( $row->calculated_cost ) : '—' ); ?></td>
|
||
<td><?php echo esc_html( isset( $row->loss_value ) && null !== $row->loss_value ? $this->format_meter_value_with_unit( $row->loss_value, $type ) : '—' ); ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<?php if ( ! $is_pdf ) : ?>
|
||
</tbody>
|
||
<?php endif; ?>
|
||
</table>
|
||
<?php
|
||
|
||
return (string) ob_get_clean();
|
||
}
|
||
|
||
/**
|
||
* Build a year/section lookup for saved consumption prices.
|
||
*
|
||
* @param array $rows Consumption rows.
|
||
* @return array
|
||
*/
|
||
private function build_consumption_rate_lookup( $rows ) {
|
||
$lookup = array();
|
||
$years = array();
|
||
|
||
foreach ( (array) $rows as $row ) {
|
||
if ( empty( $row->section_id ) || empty( $row->reading_date ) ) {
|
||
continue;
|
||
}
|
||
|
||
$years[] = (int) gmdate( 'Y', strtotime( $row->reading_date ) );
|
||
}
|
||
|
||
$years = array_values( array_unique( array_filter( $years ) ) );
|
||
|
||
foreach ( $years as $year ) {
|
||
$rates = $this->costs->get_section_prices( $year );
|
||
|
||
foreach ( $rates as $rate ) {
|
||
$lookup[ $year ][ (int) $rate->section_id ] = $rate;
|
||
}
|
||
}
|
||
|
||
return $lookup;
|
||
}
|
||
|
||
/**
|
||
* Resolve the saved unit price for one consumption row.
|
||
*
|
||
* @param object $row Consumption row.
|
||
* @param array $rate_lookup Saved rate map.
|
||
* @return float|null
|
||
*/
|
||
private function get_consumption_unit_price( $row, $rate_lookup ) {
|
||
$year = ! empty( $row->reading_date ) ? (int) gmdate( 'Y', strtotime( $row->reading_date ) ) : 0;
|
||
$section_id = ! empty( $row->section_id ) ? (int) $row->section_id : 0;
|
||
|
||
if ( ! $year || ! $section_id || empty( $rate_lookup[ $year ][ $section_id ] ) ) {
|
||
return null;
|
||
}
|
||
|
||
$rate = $rate_lookup[ $year ][ $section_id ];
|
||
|
||
if ( 'power' === $row->type ) {
|
||
return null !== $rate->power_price_per_kwh ? (float) $rate->power_price_per_kwh : null;
|
||
}
|
||
|
||
return null !== $rate->water_price_per_m3 ? (float) $rate->water_price_per_m3 : null;
|
||
}
|
||
|
||
/**
|
||
* Calculate the cost for a consumption delta.
|
||
*
|
||
* @param float|int|null $consumption Consumption amount.
|
||
* @param string $type Meter type.
|
||
* @param float|null $unit_price Saved price per unit.
|
||
* @return float|null
|
||
*/
|
||
private function calculate_consumption_cost( $consumption, $type, $unit_price ) {
|
||
if ( null === $consumption || null === $unit_price ) {
|
||
return null;
|
||
}
|
||
|
||
$amount = (float) $consumption;
|
||
|
||
if ( 'power' === $type ) {
|
||
$settings = wp_parse_args( get_option( 'kgvvm_settings', array() ), $this->get_settings_defaults() );
|
||
|
||
if ( 'mwh' === $settings['power_unit'] ) {
|
||
$amount *= 1000;
|
||
}
|
||
}
|
||
|
||
return $amount * (float) $unit_price;
|
||
}
|
||
|
||
/**
|
||
* Return the display unit for a meter type.
|
||
*
|
||
* @param string $type Meter type.
|
||
* @return string
|
||
*/
|
||
private function meter_unit_label( $type ) {
|
||
if ( 'water' === $type ) {
|
||
return 'm³';
|
||
}
|
||
|
||
$settings = wp_parse_args( get_option( 'kgvvm_settings', array() ), $this->get_settings_defaults() );
|
||
return 'mwh' === $settings['power_unit'] ? 'MWh' : 'kWh';
|
||
}
|
||
|
||
/**
|
||
* Build colored status badge HTML.
|
||
*
|
||
* @param string $label Visible label.
|
||
* @param string $color green|orange|gray.
|
||
* @return string
|
||
*/
|
||
private function status_badge( $label, $color ) {
|
||
return '<span class="kgvvm-status kgvvm-status--' . esc_attr( $color ) . '">' . esc_html( $label ) . '</span>';
|
||
}
|
||
|
||
/**
|
||
* Human parcel status label.
|
||
*
|
||
* @param string $status Raw status.
|
||
* @return string
|
||
*/
|
||
private function parcel_status_label( $status ) {
|
||
$map = array(
|
||
'free' => __( 'Frei', KGVVM_TEXT_DOMAIN ),
|
||
'assigned' => __( 'Vergeben', KGVVM_TEXT_DOMAIN ),
|
||
'reserved' => __( 'Reserviert', KGVVM_TEXT_DOMAIN ),
|
||
'inactive' => __( 'Inaktiv', KGVVM_TEXT_DOMAIN ),
|
||
);
|
||
|
||
return isset( $map[ $status ] ) ? $map[ $status ] : $status;
|
||
}
|
||
|
||
/**
|
||
* Human meter type label.
|
||
*
|
||
* @param string $type water|power.
|
||
* @return string
|
||
*/
|
||
private function meter_type_label( $type ) {
|
||
return 'water' === $type ? __( 'Wasser', KGVVM_TEXT_DOMAIN ) : __( 'Strom', KGVVM_TEXT_DOMAIN );
|
||
}
|
||
|
||
/**
|
||
* Format a short meter label for lists and select boxes.
|
||
*
|
||
* @param object $meter Meter record.
|
||
* @return string
|
||
*/
|
||
private function format_meter_short_label( $meter ) {
|
||
$parts = array(
|
||
$this->meter_type_label( $meter->type ),
|
||
$meter->meter_number,
|
||
);
|
||
|
||
if ( ! empty( $meter->calibration_year ) ) {
|
||
$parts[] = sprintf( __( 'Eichjahr %s', KGVVM_TEXT_DOMAIN ), $meter->calibration_year );
|
||
}
|
||
|
||
return implode( ' – ', array_filter( $parts ) );
|
||
}
|
||
|
||
/**
|
||
* Format one member label for tenant views.
|
||
*
|
||
* @param object $member Member row.
|
||
* @return string
|
||
*/
|
||
private function format_tenant_member_label( $member ) {
|
||
$label = $member->display_name;
|
||
|
||
if ( ! empty( $member->parcel_label ) ) {
|
||
$label .= ' – ' . sprintf( __( 'Parzelle %s', KGVVM_TEXT_DOMAIN ), $member->parcel_label );
|
||
}
|
||
|
||
return $label;
|
||
}
|
||
|
||
/**
|
||
* Format a full tenant name.
|
||
*
|
||
* @param object $tenant Tenant row.
|
||
* @return string
|
||
*/
|
||
private function format_tenant_full_name( $tenant ) {
|
||
return trim( $tenant->last_name . ', ' . $tenant->first_name, ' ,' );
|
||
}
|
||
|
||
/**
|
||
* Ensure capability.
|
||
*
|
||
* @param string $cap Capability name.
|
||
* @return void
|
||
*/
|
||
private function require_cap( $cap ) {
|
||
if ( ! current_user_can( $cap ) ) {
|
||
wp_die( esc_html__( 'Sie haben keine Berechtigung für diese Aktion.', KGVVM_TEXT_DOMAIN ) );
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Render one-time admin notice.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function render_notice() {
|
||
$key = 'kgvvm_notice_' . get_current_user_id();
|
||
$notice = get_transient( $key );
|
||
|
||
if ( empty( $notice['message'] ) ) {
|
||
return;
|
||
}
|
||
|
||
delete_transient( $key );
|
||
$type = ! empty( $notice['type'] ) ? $notice['type'] : 'success';
|
||
|
||
switch ( $type ) {
|
||
case 'warning':
|
||
$class = 'notice notice-warning';
|
||
break;
|
||
case 'success':
|
||
$class = 'notice notice-success is-dismissible';
|
||
break;
|
||
default:
|
||
$class = 'notice notice-error';
|
||
break;
|
||
}
|
||
|
||
echo '<div class="' . esc_attr( $class ) . '"><p>' . esc_html( $notice['message'] ) . '</p></div>';
|
||
}
|
||
|
||
/**
|
||
* Redirect with a transient notice.
|
||
*
|
||
* @param string $page Target page slug.
|
||
* @param string $type success|error.
|
||
* @param string $message Notice text.
|
||
* @param array $args Extra query args.
|
||
* @return void
|
||
*/
|
||
private function redirect_with_notice( $page, $type, $message, $args = array() ) {
|
||
set_transient(
|
||
'kgvvm_notice_' . get_current_user_id(),
|
||
array(
|
||
'type' => $type,
|
||
'message' => wp_strip_all_tags( $message ),
|
||
),
|
||
60
|
||
);
|
||
|
||
wp_safe_redirect( $this->admin_url( $page, $args ) );
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* Build admin page URL.
|
||
*
|
||
* @param string $page Page slug.
|
||
* @param array $args Additional args.
|
||
* @return string
|
||
*/
|
||
private function admin_url( $page, $args = array() ) {
|
||
return add_query_arg( array_merge( array( 'page' => $page ), $args ), admin_url( 'admin.php' ) );
|
||
}
|
||
|
||
/**
|
||
* Build sortable list URL while preserving filters.
|
||
*
|
||
* @param string $page Page slug.
|
||
* @param string $column Sort column.
|
||
* @return string
|
||
*/
|
||
private function sort_url( $page, $column ) {
|
||
$current_orderby = isset( $_GET['orderby'] ) ? sanitize_key( wp_unslash( $_GET['orderby'] ) ) : '';
|
||
$current_order = isset( $_GET['order'] ) ? strtolower( sanitize_key( wp_unslash( $_GET['order'] ) ) ) : 'asc';
|
||
$next_order = ( $current_orderby === $column && 'asc' === $current_order ) ? 'desc' : 'asc';
|
||
|
||
$args = array();
|
||
|
||
foreach ( $_GET as $key => $value ) {
|
||
if ( in_array( $key, array( '_wpnonce', 'kgvvm_action', 'view', 'id' ), true ) ) {
|
||
continue;
|
||
}
|
||
$args[ $key ] = wp_unslash( $value );
|
||
}
|
||
|
||
$args['page'] = $page;
|
||
$args['orderby'] = $column;
|
||
$args['order'] = $next_order;
|
||
|
||
return add_query_arg( $args, admin_url( 'admin.php' ) );
|
||
}
|
||
}
|